<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

/**
 * A sales order becomes a document in its own right.
 *
 * Until now "sales order" was only a `channel` value on `invoices`, which meant
 * taking an order minted an invoice number and moved stock the same instant —
 * so an order could never sit in a not-yet-invoiced state, which is the whole
 * point of the module (bulk/custom orders taken now, produced and invoiced
 * later, against a delivery deadline). These tables give the order its own
 * SO- number, its own lifecycle status (the `sales_order` status context that
 * was already seeded but unused), and its own deadline. Stock is untouched
 * until the order is converted, at which point the existing InvoiceService
 * mints the real invoice exactly as before.
 *
 * Custom order lines carry no `product_id` at all: the customer describes what
 * they want, staff make it to order, and nothing is drawn from inventory —
 * InvoiceService::sellLine() already sells a product-less line as a free-form
 * item with no stock movement, so conversion needs no special case.
 */
return new class extends Migration
{
    public function up(): void
    {
        Schema::create('sales_orders', function (Blueprint $table) {
            $table->id();
            // No DB-level FK on tenant_id (integrity comes from BelongsToTenant +
            // TenantScope), matching every other owned table in this app.
            $table->unsignedBigInteger('tenant_id')->nullable()->index();
            $table->ulid('uuid');
            $table->string('order_number', 30); // SO-YYYY-NNNNN, unique per tenant
            $table->foreignId('branch_id')->nullable()->constrained('warehouses')->cascadeOnUpdate()->nullOnDelete();
            $table->foreignId('customer_id')->nullable()->constrained('customers')->cascadeOnUpdate()->nullOnDelete();
            $table->foreignId('delivery_address_id')->nullable()->constrained('delivery_addresses')->cascadeOnUpdate()->nullOnDelete();
            $table->foreignId('sales_rep_id')->nullable()->constrained('sales_reps')->cascadeOnUpdate()->nullOnDelete();

            // status_id  → statuses(context = sales_order)     — where the work is at
            // payment_status_id → statuses(context = invoice_payment) — tracked
            // independently, so an order can be paid-in-advance while still in
            // production, or invoiced while still unpaid.
            $table->foreignId('status_id')->constrained('statuses')->cascadeOnUpdate()->restrictOnDelete();
            $table->foreignId('payment_status_id')->constrained('statuses')->cascadeOnUpdate()->restrictOnDelete();

            $table->date('order_date');
            $table->date('deadline_date')->nullable();

            $table->decimal('subtotal', 14, 2)->default(0);
            $table->decimal('vat_total', 14, 2)->default(0);
            $table->enum('discount_type', ['fixed', 'percentage'])->nullable();
            $table->decimal('discount_value', 14, 2)->default(0);
            $table->decimal('discount', 14, 2)->default(0);
            $table->decimal('net_total', 14, 2)->default(0);
            $table->decimal('advance_paid', 14, 2)->default(0);
            $table->decimal('balance', 14, 2)->default(0);

            $table->text('note')->nullable();

            // Set once the order is turned into a real invoice — the order row
            // stays as the permanent record of what was ordered and when it was
            // due, and the invoice carries the money and the stock.
            $table->foreignId('converted_invoice_id')->nullable()->constrained('invoices')->cascadeOnUpdate()->nullOnDelete();
            $table->timestamp('converted_at')->nullable();

            $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
            $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();
            $table->foreignId('deleted_by')->nullable()->constrained('users')->nullOnDelete();
            $table->timestamps();
            $table->softDeletes();

            $table->unique(['tenant_id', 'order_number']);
            $table->unique(['tenant_id', 'uuid']);
            $table->index(['branch_id', 'order_date']);
            $table->index(['customer_id', 'payment_status_id']);
            // Drives the "due soon / overdue" listing and highlight.
            $table->index(['deadline_date', 'status_id']);
        });

        Schema::create('sales_order_items', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('tenant_id')->nullable()->index();
            $table->foreignId('sales_order_id')->constrained('sales_orders')->cascadeOnUpdate()->cascadeOnDelete();

            // Null for a custom line — there is no catalog product behind it.
            $table->foreignId('product_id')->nullable()->constrained('products')->cascadeOnUpdate()->nullOnDelete();
            $table->foreignId('unit_id')->nullable()->constrained('unit_of_measures')->cascadeOnUpdate()->nullOnDelete();
            $table->boolean('is_custom')->default(false);

            $table->string('product_name', 255);
            $table->string('product_sku', 60)->nullable();
            // The two free-text fields a custom order actually needs: what it is,
            // and the exact spec the customer gave us (sizes, colours, wording…).
            $table->text('custom_description')->nullable();
            $table->text('custom_specification')->nullable();

            $table->decimal('quantity', 12, 4);
            $table->decimal('unit_price', 12, 2);
            $table->decimal('vat_rate', 5, 2)->default(0);
            $table->decimal('vat_amount', 14, 2)->default(0);
            $table->decimal('gross_total', 14, 2)->default(0);
            $table->enum('discount_type', ['fixed', 'percentage'])->nullable();
            $table->decimal('discount_value', 12, 2)->default(0);
            $table->decimal('discount_amount', 14, 2)->default(0);
            $table->decimal('line_total', 14, 2)->default(0);

            $table->timestamps();

            $table->index('product_id');
            // The Custom Orders listing reads exactly this slice.
            $table->index(['tenant_id', 'is_custom']);
        });

        Schema::create('sales_order_payments', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('tenant_id')->nullable()->index();
            $table->foreignId('sales_order_id')->constrained('sales_orders')->cascadeOnUpdate()->cascadeOnDelete();

            // Advances taken against the order. These are held as intent and
            // replayed as real payment splits (and credit-note redemptions) by
            // SalesOrderService::convertToInvoice(), so the money lands through
            // the same PaymentService path every other checkout uses.
            $table->string('method', 40);
            $table->decimal('amount', 14, 2);
            $table->string('reference', 100)->nullable();
            $table->string('cheque_number', 50)->nullable();
            $table->string('cheque_bank', 100)->nullable();
            $table->string('cheque_branch', 100)->nullable();
            $table->date('cheque_date')->nullable();
            $table->foreignId('credit_note_id')->nullable()->constrained('credit_notes')->cascadeOnUpdate()->nullOnDelete();
            $table->date('received_at')->nullable();

            $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('sales_order_payments');
        Schema::dropIfExists('sales_order_items');
        Schema::dropIfExists('sales_orders');
    }
};
