Overview
The BillingDocumentItem model represents individual line items within a billing document. Each item can reference inventory products, service orders, or be standalone descriptions. Items track quantity, pricing, and tax calculations.
Model Definition
namespace App\Models;
class BillingDocumentItem extends Model
{
protected $fillable = [
'billing_document_id',
'inventory_item_id',
'order_id',
'item_kind',
'description',
'quantity',
'unit_price',
'line_subtotal',
'line_vat',
'line_total',
];
protected function casts(): array
{
return [
'quantity' => 'decimal:2',
'unit_price' => 'decimal:2',
'line_subtotal' => 'decimal:2',
'line_vat' => 'decimal:2',
'line_total' => 'decimal:2',
];
}
}
Attributes
Foreign key to the parent billing document
Optional reference to an inventory item for product sales
Optional reference to a service order for repair charges
Type of line item: product, service, or custom
Item description shown on the invoice
Quantity of items (can be fractional for services billed by hours)
Price per unit before tax
Subtotal for this line (quantity × unit_price)
VAT/tax amount for this line
Total for this line (line_subtotal + line_vat)
Relationships
Parent Document
public function document(): BelongsTo
{
return $this->belongsTo(BillingDocument::class, 'billing_document_id');
}
Access the parent billing document:
$item = BillingDocumentItem::find(1);
$document = $item->document;
$documentNumber = $document->document_number;
Inventory Item
public function inventoryItem(): BelongsTo
{
return $this->belongsTo(InventoryItem::class);
}
For product line items, reference the inventory:
$item = BillingDocumentItem::with('inventoryItem')->find(1);
if ($item->inventory_item_id) {
$productName = $item->inventoryItem->name;
$sku = $item->inventoryItem->sku;
}
Service Order
public function order(): BelongsTo
{
return $this->belongsTo(Order::class);
}
For service charges, reference the work order:
$item = BillingDocumentItem::with('order')->find(1);
if ($item->order_id) {
$orderNumber = $item->order->id;
$technician = $item->order->technician;
}
Usage Examples
Creating Product Line Items
use App\Models\BillingDocumentItem;
$item = BillingDocumentItem::create([
'billing_document_id' => $document->id,
'inventory_item_id' => $inventoryItem->id,
'item_kind' => 'product',
'description' => $inventoryItem->name,
'quantity' => 2,
'unit_price' => $inventoryItem->sale_price,
'line_subtotal' => $inventoryItem->sale_price * 2,
'line_vat' => ($inventoryItem->sale_price * 2) * ($vatRate / 100),
'line_total' => $lineSubtotal + $lineVat,
]);
Creating Service Line Items
$item = BillingDocumentItem::create([
'billing_document_id' => $document->id,
'order_id' => $order->id,
'item_kind' => 'service',
'description' => 'Reparación de ' . $equipment->type,
'quantity' => 1,
'unit_price' => $order->estimated_cost,
'line_subtotal' => $order->estimated_cost,
'line_vat' => $order->estimated_cost * ($vatRate / 100),
'line_total' => $lineSubtotal + $lineVat,
]);
Calculating Line Totals
// Manual calculation
$quantity = 3;
$unitPrice = 150.00;
$vatRate = 16; // 16%
$lineSubtotal = $quantity * $unitPrice;
$lineVat = $lineSubtotal * ($vatRate / 100);
$lineTotal = $lineSubtotal + $lineVat;
// Using BillingService
$item = $billingService->createLineItem([
'description' => 'Cable HDMI 2m',
'quantity' => 3,
'unit_price' => 150.00,
]);
Querying Line Items
// Get all items for a document
$items = BillingDocumentItem::where('billing_document_id', $documentId)
->with(['inventoryItem', 'order'])
->get();
// Get product sales only
$productItems = BillingDocumentItem::where('item_kind', 'product')
->whereNotNull('inventory_item_id')
->get();
// Get service charges only
$serviceItems = BillingDocumentItem::where('item_kind', 'service')
->whereNotNull('order_id')
->get();
Item Kinds
The item_kind field categorizes line items:
| Kind | Description | Typical Use |
|---|
product | Physical inventory item | Parts, accessories sold from stock |
service | Labor or service charge | Repair work, diagnostic fees |
custom | Freeform item | One-off charges, discounts, adjustments |
Validation Rules
When creating billing document items, the system enforces:
// app/Http/Requests/StoreBillingDocumentRequest.php
'items.*.description' => 'required|string|max:255',
'items.*.quantity' => 'required|numeric|min:0.01',
'items.*.unit_price' => 'required|numeric|min:0',
Best Practices
1. Always Calculate Totals
Don’t trust user input for calculated fields:
$lineSubtotal = $quantity * $unitPrice;
$lineVat = round($lineSubtotal * ($vatRate / 100), 2);
$lineTotal = $lineSubtotal + $lineVat;
2. Track Inventory References
For product items, always link to inventory:
if ($itemKind === 'product' && $inventoryItemId) {
$item->inventory_item_id = $inventoryItemId;
}
3. Preserve Descriptions
Store item descriptions at billing time (don’t rely on joins):
// Good: Frozen description
'description' => $inventoryItem->name,
// Bad: Dynamic description (inventory name might change)
'description' => '', // and later join to inventory
Database Schema
CREATE TABLE billing_document_items (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
billing_document_id BIGINT UNSIGNED NOT NULL,
inventory_item_id BIGINT UNSIGNED NULL,
order_id BIGINT UNSIGNED NULL,
item_kind VARCHAR(255) NOT NULL,
description VARCHAR(255) NOT NULL,
quantity DECIMAL(10,2) NOT NULL,
unit_price DECIMAL(10,2) NOT NULL,
line_subtotal DECIMAL(10,2) NOT NULL,
line_vat DECIMAL(10,2) NOT NULL,
line_total DECIMAL(10,2) NOT NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
FOREIGN KEY (billing_document_id) REFERENCES billing_documents(id) ON DELETE CASCADE,
FOREIGN KEY (inventory_item_id) REFERENCES inventory_items(id) ON DELETE SET NULL,
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE SET NULL
);