# Task 13 — Quotations

## Goal
Quotation flow: create → send to customer → accept/reject → convert to Sales Order.

---

## Migrations

### Table: `sales_quotations`
```php
Schema::create('sales_quotations', function (Blueprint $table) {
    $table->id();
    $table->string('quotation_number')->unique();
    $table->date('quotation_date');
    $table->date('expiry_date')->nullable();
    $table->unsignedBigInteger('customer_id');
    $table->unsignedBigInteger('branch_id');
    $table->string('currency_code', 3)->default('USD');
    $table->decimal('exchange_rate', 18, 6)->default(1);
    $table->decimal('subtotal', 18, 2)->default(0);
    $table->decimal('discount_amount', 18, 2)->default(0);
    $table->decimal('tax_amount', 18, 2)->default(0);
    $table->decimal('total_amount', 18, 2)->default(0);
    $table->enum('status', ['draft','sent','accepted','rejected','expired','converted'])->default('draft');
    $table->json('notes')->nullable();
    $table->unsignedBigInteger('converted_order_id')->nullable();
    $table->timestamp('sent_at')->nullable();
    $table->unsignedBigInteger('created_by')->nullable();
    $table->unsignedBigInteger('updated_by')->nullable();
    $table->timestamps();

    $table->foreign('customer_id')->references('id')->on('customers');
    $table->foreign('branch_id')->references('id')->on('branches');
    $table->foreign('converted_order_id')->references('id')->on('sales_orders')->nullOnDelete();
    $table->index(['customer_id', 'status']);
});
```

### Table: `sales_quotation_lines`
```php
Schema::create('sales_quotation_lines', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('sales_quotation_id');
    $table->unsignedBigInteger('item_id');
    $table->json('description')->nullable();
    $table->decimal('quantity', 18, 2);
    $table->decimal('unit_price', 18, 2);
    $table->decimal('discount_percent', 5, 2)->default(0);
    $table->decimal('discount_amount', 18, 2)->default(0);
    $table->unsignedBigInteger('tax_rate_id')->nullable();
    $table->decimal('tax_amount', 18, 2)->default(0);
    $table->decimal('line_subtotal', 18, 2)->default(0);
    $table->decimal('line_total', 18, 2)->default(0);
    $table->timestamps();

    $table->foreign('sales_quotation_id')->references('id')->on('sales_quotations')->cascadeOnDelete();
    $table->foreign('item_id')->references('id')->on('items');
    $table->foreign('tax_rate_id')->references('id')->on('sales_tax_rates')->nullOnDelete();
});
```

---

## Service: `app/Services/Sales/Quotation/QuotationService.php`

### `convertToOrder(SalesQuotation $quotation): SalesOrder`

```php
public function convertToOrder(SalesQuotation $quotation): SalesOrder
{
    if ($quotation->status !== 'accepted') {
        throw new \Exception('Only accepted quotations can be converted to orders.');
    }
    if ($quotation->expiry_date && $quotation->expiry_date->isPast()) {
        throw new \Exception('Quotation has expired and cannot be converted.');
    }

    return DB::transaction(function () use ($quotation) {
        // Build order lines from quotation lines
        $lines = $quotation->lines->map(fn($ql) => [
            'item_id'          => $ql->item_id,
            'description'      => $ql->description,
            'quantity'         => $ql->quantity,
            'unit_price'       => $ql->unit_price,
            'discount_percent' => $ql->discount_percent,
            'tax_rate_id'      => $ql->tax_rate_id,
        ])->toArray();

        $order = app(SalesOrderService::class)->create([
            'quotation_id'  => $quotation->id,
            'customer_id'   => $quotation->customer_id,
            'branch_id'     => $quotation->branch_id,
            'order_date'    => now()->toDateString(),
            'currency_code' => $quotation->currency_code,
            'lines'         => $lines,
        ]);

        $quotation->update([
            'status'             => 'converted',
            'converted_order_id' => $order->id,
        ]);

        return $order;
    });
}
```

### `markExpired(): int`
Scheduled command — marks quotations past expiry_date as `expired`. Returns count.

---

## Status Machine
```
draft ──send──► sent ──accept──► accepted ──convert──► converted
                   └──reject──► rejected
draft/sent/accepted (if past expiry) → expired (via scheduled job)
```

---

## Permissions
```
sales.quotations.view
sales.quotations.create
sales.quotations.update
sales.quotations.send
sales.quotations.accept
sales.quotations.reject
sales.quotations.convert
```
