# Task 14 — Sales Order Expansion

## Goal
Add discounts, line-level tax, header totals, invoiced tracking, and status machine to the existing Sales Order.

---

## Migration: Alter `sales_orders`

```php
Schema::table('sales_orders', function (Blueprint $table) {
    $table->unsignedBigInteger('quotation_id')->nullable()->after('branch_id');
    $table->enum('discount_type', ['percent', 'amount'])->default('amount')->after('notes');
    $table->decimal('discount_value', 18, 2)->default(0)->after('discount_type');
    $table->decimal('discount_amount', 18, 2)->default(0)->after('discount_value');
    $table->decimal('subtotal', 18, 2)->default(0)->after('discount_amount');
    $table->decimal('tax_amount', 18, 2)->default(0)->after('subtotal');
    $table->decimal('total_amount', 18, 2)->default(0)->after('tax_amount');
    $table->decimal('invoiced_amount', 18, 2)->default(0)->after('total_amount');
    $table->date('delivery_date')->nullable()->after('invoiced_amount');
    $table->timestamp('confirmed_at')->nullable()->after('delivery_date');

    $table->foreign('quotation_id')->references('id')->on('sales_quotations')->nullOnDelete();
    $table->index('quotation_id');
});
```

## Migration: Alter `sales_order_lines`

```php
Schema::table('sales_order_lines', function (Blueprint $table) {
    $table->decimal('discount_percent', 5, 2)->default(0)->after('unit_price');
    $table->decimal('discount_amount', 18, 2)->default(0)->after('discount_percent');
    $table->unsignedBigInteger('tax_rate_id')->nullable()->after('discount_amount');
    $table->decimal('tax_amount', 18, 2)->default(0)->after('tax_rate_id');
    $table->decimal('line_subtotal', 18, 2)->default(0)->after('tax_amount'); // qty × price
    // line_total already exists — rename meaning: after discount + tax
    $table->decimal('invoiced_quantity', 18, 2)->default(0)->after('line_total');

    $table->foreign('tax_rate_id')->references('id')->on('sales_tax_rates')->nullOnDelete();
});
```

---

## Update Service: `app/Services/Sales/SalesOrderService.php`

### `calculateLineTotals(array $line): array`
```php
private function calculateLineTotals(array $line): array
{
    $line_subtotal    = round($line['quantity'] * $line['unit_price'], 2);
    $discount_amount  = round($line_subtotal * (($line['discount_percent'] ?? 0) / 100), 2);
    $taxable          = $line_subtotal - $discount_amount;
    $tax_amount       = 0;

    if (!empty($line['tax_rate_id'])) {
        $rate       = SalesTaxRate::find($line['tax_rate_id']);
        $tax_amount = $rate ? round($taxable * ($rate->rate / 100), 2) : 0;
    }

    $line_total = $taxable + $tax_amount;

    return array_merge($line, compact('line_subtotal', 'discount_amount', 'tax_amount', 'line_total'));
}
```

### `calculateOrderTotals(array $lines, array $header): array`
```php
private function calculateOrderTotals(array $lines, array $header): array
{
    $subtotal   = array_sum(array_column($lines, 'line_subtotal'));
    $tax_amount = array_sum(array_column($lines, 'tax_amount'));

    // Header-level discount applied after line totals
    $discount_amount = 0;
    if ($header['discount_type'] === 'percent') {
        $discount_amount = round($subtotal * ($header['discount_value'] / 100), 2);
    } else {
        $discount_amount = (float) ($header['discount_value'] ?? 0);
    }

    $total_amount = $subtotal - $discount_amount + $tax_amount;

    return compact('subtotal', 'discount_amount', 'tax_amount', 'total_amount');
}
```

### `confirm(SalesOrder $order): SalesOrder`
```php
public function confirm(SalesOrder $order): SalesOrder
{
    if ($order->status !== 'draft') {
        throw new \Exception('Only draft orders can be confirmed.');
    }
    $order->update([
        'status'       => 'confirmed',
        'confirmed_at' => now(),
    ]);
    return $order->fresh();
}
```

### `updateInvoicedAmount(SalesOrder $order): void`
Called after an invoice is created/cancelled against this order.
```php
public function updateInvoicedAmount(SalesOrder $order): void
{
    $invoiced = $order->invoices()
        ->whereNotIn('status', ['cancelled'])
        ->sum('total_amount');

    $status = match (true) {
        $invoiced <= 0               => 'confirmed',
        $invoiced < $order->total_amount => 'partially_invoiced',
        default                      => 'invoiced',
    };

    $order->update(['invoiced_amount' => $invoiced, 'status' => $status]);
}
```

---

## Status Machine
```
draft ──confirm──► confirmed ──(invoice created)──► partially_invoiced ──► invoiced
  └──cancel──► cancelled
confirmed ──cancel──► cancelled (no invoices)
```

---

## Update Views

Update `sales_orders/create.blade.php` and `edit.blade.php`:
- Add Discount Type toggle (percent / amount) + Discount Value field
- Add Tax Rate dropdown per line (select from sales_tax_rates)
- Add Discount Percent per line
- Show calculated totals (subtotal, discount, tax, total) in summary footer
- Use JavaScript to auto-calculate totals on the fly as user types

Update `sales_orders/show.blade.php`:
- Show financial summary card
- Show Confirm button (if draft)
- Show invoiced amount vs total amount progress bar
- Show "Create Invoice" button (if confirmed or partially_invoiced)
