The Laravel & Vue 3 Invoice Product System is a modern, scalable, and high-performance billing solution designed for businesses, startups, and software products that require reliable invoice and product management. Built using Laravel’s powerful backend API and Vue 3’s reactive frontend, this system ensures smooth data handling, real-time calculations, and a professional user experience.
This invoice system allows users to manage products, generate invoices dynamically, calculate taxes automatically, and maintain accurate financial records. Its modular architecture makes it ideal for SaaS platforms, ERP systems, accounting software, and custom business applications.
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->decimal('price', 10, 2);
$table->timestamps();
});
Schema::create('invoices', function (Blueprint $table) {
$table->id();
$table->string('invoice_no')->unique();
$table->date('invoice_date');
$table->decimal('sub_total', 10, 2);
$table->decimal('tax', 10, 2)->default(0);
$table->decimal('grand_total', 10, 2);
$table->timestamps();
});
Schema::create('invoice_items', function (Blueprint $table) {
$table->id();
$table->foreignId('invoice_id')->constrained()->cascadeOnDelete();
$table->foreignId('product_id')->constrained();
$table->integer('qty');
$table->decimal('price', 10, 2);
$table->decimal('total', 10, 2);
$table->timestamps();
});
class Invoice extends Model
{
protected $fillable = ['invoice_no','invoice_date','sub_total','tax','grand_total'];
public function items()
{
return $this->hasMany(InvoiceItem::class);
}
}
class InvoiceItem extends Model
{
protected $fillable = ['invoice_id','product_id','qty','price','total'];
public function product()
{
return $this->belongsTo(Product::class);
}
}
class InvoiceController extends Controller
{
public function products()
{
return Product::all();
}
public function store(Request $request)
{
DB::transaction(function () use ($request) {
$invoice = Invoice::create([
'invoice_no' => 'INV-' . time(),
'invoice_date' => now(),
'sub_total' => $request->sub_total,
'tax' => $request->tax,
'grand_total' => $request->grand_total,
]);
foreach ($request->items as $item) {
$invoice->items()->create([
'product_id' => $item['product_id'],
'qty' => $item['qty'],
'price' => $item['price'],
'total' => $item['total'],
]);
}
});
return response()->json(['message' => 'Invoice created successfully']);
}
}
Route::get('/products', [InvoiceController::class, 'products']);
Route::post('/invoices', [InvoiceController::class, 'store']);
<script setup>
import { ref, computed, onMounted } from 'vue'
import axios from 'axios'
const products = ref([])
const items = ref([
{ product_id: '', qty: 1, price: 0, total: 0 }
])
const taxRate = ref(5)
onMounted(async () => {
const res = await axios.get('/api/products')
products.value = res.data
})
const addRow = () => {
items.value.push({ product_id: '', qty: 1, price: 0, total: 0 })
}
const removeRow = (i) => items.value.splice(i, 1)
const updateItem = (item) => {
item.total = item.qty * item.price
}
const subTotal = computed(() =>
items.value.reduce((sum, i) => sum + i.total, 0)
)
const tax = computed(() => (subTotal.value * taxRate.value) / 100)
const grandTotal = computed(() => subTotal.value + tax.value)
const submitInvoice = async () => {
await axios.post('/api/invoices', {
items: items.value,
sub_total: subTotal.value,
tax: tax.value,
grand_total: grandTotal.value,
})
alert('Invoice saved')
}
</script>
<template>
<div class="p-6 max-w-4xl mx-auto">
<h2 class="text-xl font-bold mb-4">Create Invoice</h2>
<table class="w-full border">
<thead class="bg-gray-100">
<tr>
<th>Product</th>
<th>Qty</th>
<th>Price</th>
<th>Total</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="(item,i) in items" :key="i">
<td>
<select v-model="item.product_id"
@change="item.price = products.find(p=>p.id==item.product_id)?.price">
<option value="">Select</option>
<option v-for="p in products" :value="p.id">{{ p.name }}</option>
</select>
</td>
<td><input type="number" v-model="item.qty" @input="updateItem(item)" /></td>
<td><input type="number" v-model="item.price" @input="updateItem(item)" /></td>
<td>{{ item.total }}</td>
<td><button @click="removeRow(i)">❌</button></td>
</tr>
</tbody>
</table>
<button @click="addRow" class="mt-2">➕ Add</button>
<div class="mt-4 text-right">
<p>Sub Total: {{ subTotal }}</p>
<p>Tax ({{ taxRate }}%): {{ tax }}</p>
<p class="font-bold">Grand Total: {{ grandTotal }}</p>
</div>
<button @click="submitInvoice" class="mt-4 bg-black text-white px-4 py-2">Save Invoice</button>
</div>
</template>