sed -n '1,240p' app/Http/Resources/Admin/V1/OrderResource.php

<?php

namespace App\Http\Resources\Admin\V1;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class OrderResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'store_id' => $this->store_id,
            'order_number' => $this->order_number,
            'email' => $this->email,
            'customer' => $this->whenLoaded('customer', fn () => $this->customer ? [
                'id' => $this->customer->id,
                'name' => $this->customer->name,
                'email' => $this->customer->email,
            ] : null),
            'status' => $this->status?->value,
            'financial_status' => $this->financial_status?->value,
            'fulfillment_status' => $this->fulfillment_status?->value,
            'payment_method' => $this->payment_method?->value,
            'currency' => $this->currency,
            'subtotal_amount' => $this->subtotal_amount,
            'discount_amount' => $this->discount_amount,
            'shipping_amount' => $this->shipping_amount,
            'tax_amount' => $this->tax_amount,
            'total_amount' => $this->total_amount,
            'lines_count' => $this->whenCounted('lines'),
            'lines' => $this->whenLoaded('lines', fn () => $this->lines->map(fn ($line): array => [
                'id' => $line->id,
                'product_id' => $line->product_id,
                'variant_id' => $line->variant_id,
                'title' => $line->title_snapshot,
                'sku' => $line->sku_snapshot,
                'quantity' => $line->quantity,
                'unit_price_amount' => $line->unit_price_amount,
                'total_amount' => $line->total_amount,
            ])->values()),
            'payments' => $this->whenLoaded('payments', fn () => $this->payments->map(fn ($payment): array => [
                'id' => $payment->id,
                'provider' => $payment->provider,
                'method' => $payment->method?->value,
                'provider_payment_id' => $payment->provider_payment_id,
                'status' => $payment->status?->value,
                'amount' => $payment->amount,
                'currency' => $payment->currency,
            ])->values()),
            'refunds' => RefundResource::collection($this->whenLoaded('refunds')),
            'fulfillments' => FulfillmentResource::collection($this->whenLoaded('fulfillments')),
            'shipping_address' => $this->shipping_address_json,
            'billing_address' => $this->billing_address_json,
            'placed_at' => $this->placed_at?->toIso8601String(),
            'created_at' => $this->created_at?->toIso8601String(),
        ];
    }
}


find app/Models -maxdepth 1 -type f | sort | rg 'Export|Order|Payment|Fulfillment'

Question: Export|Order|Payment|Fulfillment

app/Models/Fulfillment.php
app/Models/FulfillmentLine.php
app/Models/Order.php
app/Models/OrderLine.php
app/Models/Payment.php


rg -n "exportCsv|CSV|export" app tests resources specs/progress.md

rg -n 'exportCsv|CSV|export' app tests resources specs/progress.md

Question: exportCsv|CSV|export

specs/progress.md:31:| Admin UI | `specs/03-ADMIN-UI.md` | partial | Flux/Livewire admin shell now includes dashboard KPI/date-range reporting, analytics reporting with filters/export, catalog/product/collection/inventory management, product option matrix generation, product media upload/alt text/reorder/delete controls, order/customer/discount management, page index/form, nested navigation menu editor with drag sorting and parent selection, theme grid, theme settings editor with iframe preview, theme file list/editor, general settings, domain management, shipping zone/rate management, tax settings, checkout policy settings, notification settings, search settings/reindexing, installed app list/detail, and developer token/webhook management with auth protection and store scoping. |
specs/progress.md:36:| Browser E2E plan | `specs/08-PLAYWRIGHT-E2E-PLAN.md` | partial | Manual Playwright MCP smoke coverage has verified storefront catalog browsing on `shop.test` and `acme-fashion.test`, product detail, add-to-cart, cart drawer/page, checkout through order completion, order confirmation, customer order list/detail, SQLite-backed search and search API JSON, analytics event ingestion API, DB-backed content pages, seeded nested navigation dropdowns, admin dashboard on desktop/mobile, admin analytics, admin app list/detail, admin developers token/webhook flows, admin product/collection/inventory/order/customer/discount/content/navigation/settings/theme/search pages, admin checkout/notification settings pages, admin product media section rendering, admin analytics CSV export, admin customer address creation, admin discount creation/editing/filtering, admin page creation, admin nested navigation rendering/item creation/save, admin theme editor preview, admin theme file list/editor, admin search reindexing, admin fulfillment creation/shipping/delivery, and auth flows without console warnings/errors on successful pages. Automated Pest browser smoke coverage now checks storefront core pages, authenticated admin core pages, and mobile storefront rendering for JavaScript errors. Full Spec 08 browser suites are still incomplete. |
specs/progress.md:218:- 2026-05-04: Playwright MCP verified `http://shop.test/admin/analytics` renders metrics, charts, top referrers, and CSV export; browser fetch to `/api/storefront/v1/analytics/events` returned 202 with 1 accepted event and current Playwright console checks reported no warnings/errors. Boost `browser_logs` only returned older May 3 account-login warnings from a previously fixed issue.
specs/progress.md:453:- The admin analytics CSV export is generated as a data URL for the self-contained local app rather than creating persistent export files/jobs.
specs/progress.md:468:- Admin REST export surfaces are still missing; outbound webhook subscription management is implemented through the admin UI only, as specified for the initial implementation.
resources/views/livewire/admin/analytics/index.blade.php:35:        <flux:button wire:click="exportCsv" icon="arrow-down-tray" variant="ghost" wire:loading.attr="disabled">
resources/views/livewire/admin/analytics/index.blade.php:36:            Export CSV
resources/views/livewire/admin/analytics/index.blade.php:37:            Exporting...
resources/views/livewire/admin/analytics/index.blade.php:40:        @if ($exportUrl)
resources/views/livewire/admin/analytics/index.blade.php:41:            <flux:button :href="$exportUrl" download="analytics.csv" icon="document-arrow-down" variant="filled">
app/Livewire/Admin/Analytics/Index.php:69:    public ?string $exportUrl = null;
app/Livewire/Admin/Analytics/Index.php:148:    public function exportCsv(AnalyticsService $analytics): void
app/Livewire/Admin/Analytics/Index.php:159:        $this->exportUrl = 'data:text/csv;charset=utf-8,'.rawurlencode(implode("\n", $lines));
tests/Feature/Admin/AnalyticsDashboardTest.php:56:test('admin analytics component filters and exports csv data', function (): void {
tests/Feature/Admin/AnalyticsDashboardTest.php:66:        ->call('exportCsv')
tests/Feature/Admin/AnalyticsDashboardTest.php:68:        ->assertSet('exportUrl', fn (?string $url): bool => str_starts_with((string) $url, 'data:text/csv'));


Output:
Chunk ID: e3be36
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1083
Output:
specs/progress.md:31:| Admin UI | `specs/03-ADMIN-UI.md` | partial | Flux/Livewire admin shell now includes dashboard KPI/date-range reporting, analytics reporting with filters/export, catalog/product/collection/inventory management, product option matrix generation, product media upload/alt text/reorder/delete controls, order/customer/discount management, page index/form, nested navigation menu editor with drag sorting and parent selection, theme grid, theme settings editor with iframe preview, theme file list/editor, general settings, domain management, shipping zone/rate management, tax settings, checkout policy settings, notification settings, search settings/reindexing, installed app list/detail, and developer token/webhook management with auth protection and store scoping. |
specs/progress.md:36:| Browser E2E plan | `specs/08-PLAYWRIGHT-E2E-PLAN.md` | partial | Manual Playwright MCP smoke coverage has verified storefront catalog browsing on `shop.test` and `acme-fashion.test`, product detail, add-to-cart, cart drawer/page, checkout through order completion, order confirmation, customer order list/detail, SQLite-backed search and search API JSON, analytics event ingestion API, DB-backed content pages, seeded nested navigation dropdowns, admin dashboard on desktop/mobile, admin analytics, admin app list/detail, admin developers token/webhook flows, admin product/collection/inventory/order/customer/discount/content/navigation/settings/theme/search pages, admin checkout/notification settings pages, admin product media section rendering, admin analytics CSV export, admin customer address creation, admin discount creation/editing/filtering, admin page creation, admin nested navigation rendering/item creation/save, admin theme editor preview, admin theme file list/editor, admin search reindexing, admin fulfillment creation/shipping/delivery, and auth flows without console warnings/errors on successful pages. Automated Pest browser smoke coverage now checks storefront core pages, authenticated admin core pages, and mobile storefront rendering for JavaScript errors. Full Spec 08 browser suites are still incomplete. |
specs/progress.md:218:- 2026-05-04: Playwright MCP verified `http://shop.test/admin/analytics` renders metrics, charts, top referrers, and CSV export; browser fetch to `/api/storefront/v1/analytics/events` returned 202 with 1 accepted event and current Playwright console checks reported no warnings/errors. Boost `browser_logs` only returned older May 3 account-login warnings from a previously fixed issue.
specs/progress.md:453:- The admin analytics CSV export is generated as a data URL for the self-contained local app rather than creating persistent export files/jobs.
specs/progress.md:468:- Admin REST export surfaces are still missing; outbound webhook subscription management is implemented through the admin UI only, as specified for the initial implementation.
resources/views/livewire/admin/analytics/index.blade.php:35:        <flux:button wire:click="exportCsv" icon="arrow-down-tray" variant="ghost" wire:loading.attr="disabled">
resources/views/livewire/admin/analytics/index.blade.php:36:            Export CSV
resources/views/livewire/admin/analytics/index.blade.php:37:            Exporting...
resources/views/livewire/admin/analytics/index.blade.php:40:        @if ($exportUrl)
resources/views/livewire/admin/analytics/index.blade.php:41:            <flux:button :href="$exportUrl" download="analytics.csv" icon="document-arrow-down" variant="filled">
app/Livewire/Admin/Analytics/Index.php:69:    public ?string $exportUrl = null;
app/Livewire/Admin/Analytics/Index.php:148:    public function exportCsv(AnalyticsService $analytics): void
app/Livewire/Admin/Analytics/Index.php:159:        $this->exportUrl = 'data:text/csv;charset=utf-8,'.rawurlencode(implode("\n", $lines));
tests/Feature/Admin/AnalyticsDashboardTest.php:56:test('admin analytics component filters and exports csv data', function (): void {
tests/Feature/Admin/AnalyticsDashboardTest.php:66:        ->call('exportCsv')
tests/Feature/Admin/AnalyticsDashboardTest.php:68:        ->assertSet('exportUrl', fn (?string $url): bool => str_starts_with((string) $url, 'data:text/csv'));


Output:
Chunk ID: bcab0e
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 32
Output:
app/Models/Fulfillment.php
app/Models/FulfillmentLine.php
app/Models/Order.php
app/Models/OrderLine.php
app/Models/Payment.php


Output:
Chunk ID: faf8b6
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 839
Output:
<?php

namespace App\Http\Controllers\Api\Admin\V1;

use App\Http\Controllers\Controller;
use App\Http\Resources\Admin\V1\OrderResource;
use App\Models\Order;
use App\Models\Store;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Validation\Rule;

class OrderController extends Controller
{
    public function index(Request $request, Store $store): AnonymousResourceCollection
    {
        $this->authorizeStore($request, $store);

        $validated = $request->validate([
            'status' => ['nullable', Rule::in(['pending', 'paid', 'fulfilled', 'cancelled', 'refunded'])],
            'financial_status' => ['nullable', Rule::in(['pending', 'authorized', 'paid', 'partially_refunded', 'refunded', 'voided'])],
            'fulfillment_status' => ['nullable', Rule::in(['unfulfilled', 'partial', 'fulfilled'])],
            'query' => ['nullable', 'string', 'max:255'],
            'per_page' => ['nullable', 'integer', 'min:1', 'max:100'],
        ]);

        $orders = Order::withoutGlobalScopes()
            ->with('customer')
            ->withCount('lines')
            ->where('store_id', $store->getKey())
            ->when(data_get($validated, 'status'), fn (Builder $query, string $status) => $query->where('status', $status))
            ->when(data_get($validated, 'financial_status'), fn (Builder $query, string $status) => $query->where('financial_status', $status))
            ->when(data_get($validated, 'fulfillment_status'), fn (Builder $query, string $status) => $query->where('fulfillment_status', $status))
            ->when(data_get($validated, 'query'), function (Builder $query, string $search): void {
                $like = '%'.$search.'%';

                $query->where(function (Builder $query) use ($like): void {
                    $query
                        ->where('order_number', 'like', $like)
                        ->orWhere('email', 'like', $like)
                        ->orWhereHas('customer', fn (Builder $query) => $query->where('name', 'like', $like));
                });
            })
            ->latest('placed_at')
            ->latest('id')
            ->paginate((int) data_get($validated, 'per_page', 25));

        return OrderResource::collection($orders);
    }

    public function show(Request $request, Store $store, Order $order): OrderResource
    {
        $this->authorizeStore($request, $store);
        $this->abortUnlessOrderBelongsToStore($order, $store);

        return OrderResource::make($this->loadOrder($order));
    }

    private function authorizeStore(Request $request, Store $store): void
    {
        if (! $request->attributes->has('admin_api_oauth_token')) {
            abort_unless($request->user()?->stores()->whereKey($store->getKey())->exists(), 403);
        }

        app()->instance('current_store', $store);
    }

    private function abortUnlessOrderBelongsToStore(Order $order, Store $store): void
    {
        abort_unless((int) $order->store_id === $store->getKey(), 404);
    }

    private function loadOrder(Order $order): Order
    {
        return $order->load([
            'customer',
            'lines',
            'payments',
            'refunds',
            'fulfillments.lines',
        ])->loadCount('lines');
    }
}


Output:
Chunk ID: 17b14f
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 718
Output:
<?php

namespace App\Http\Resources\Admin\V1;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class OrderResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'store_id' => $this->store_id,
            'order_number' => $this->order_number,
            'email' => $this->email,
            'customer' => $this->whenLoaded('customer', fn () => $this->customer ? [
                'id' => $this->customer->id,
                'name' => $this->customer->name,
                'email' => $this->customer->email,
            ] : null),
            'status' => $this->status?->value,
            'financial_status' => $this->financial_status?->value,
            'fulfillment_status' => $this->fulfillment_status?->value,
            'payment_method' => $this->payment_method?->value,
            'currency' => $this->currency,
            'subtotal_amount' => $this->subtotal_amount,
            'discount_amount' => $this->discount_amount,
            'shipping_amount' => $this->shipping_amount,
            'tax_amount' => $this->tax_amount,
            'total_amount' => $this->total_amount,
            'lines_count' => $this->whenCounted('lines'),
            'lines' => $this->whenLoaded('lines', fn () => $this->lines->map(fn ($line): array => [
                'id' => $line->id,
                'product_id' => $line->product_id,
                'variant_id' => $line->variant_id,
                'title' => $line->title_snapshot,
                'sku' => $line->sku_snapshot,
                'quantity' => $line->quantity,
                'unit_price_amount' => $line->unit_price_amount,
                'total_amount' => $line->total_amount,
            ])->values()),
            'payments' => $this->whenLoaded('payments', fn () => $this->payments->map(fn ($payment): array => [
                'id' => $payment->id,
                'provider' => $payment->provider,
                'method' => $payment->method?->value,
                'provider_payment_id' => $payment->provider_payment_id,
                'status' => $payment->status?->value,
                'amount' => $payment->amount,
                'currency' => $payment->currency,
            ])->values()),
            'refunds' => RefundResource::collection($this->whenLoaded('refunds')),
            'fulfillments' => FulfillmentResource::collection($this->whenLoaded('fulfillments')),
            'shipping_address' => $this->shipping_address_json,
            'billing_address' => $this->billing_address_json,
            'placed_at' => $this->placed_at?->toIso8601String(),
            'created_at' => $this->created_at?->toIso8601String(),
        ];
    }
}


sed -n '1,260p' database/factories/OrderFactory.php


sed -n '1,220p' app/Models/Fulfillment.php


sed -n '1,220p' database/migrations/*create_orders* 2>/dev/null


sed -n '1,260p' tests/Feature/Api/AdminOrderApiTest.php


rg -n "enum .*Status|case Completed|ExportStatus" app/Enums app/Models database/factories


/bin/zsh
-lc
sed -n '1,260p' app/Models/Order.php

<?php

namespace App\Models;

use App\Enums\FinancialStatus;
use App\Enums\FulfillmentStatus;
use App\Enums\OrderStatus;
use App\Enums\PaymentMethod;
use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Order extends Model
{
    /** @use HasFactory<\Database\Factories\OrderFactory> */
    use BelongsToStore, HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'checkout_id',
        'customer_id',
        'order_number',
        'payment_method',
        'status',
        'financial_status',
        'fulfillment_status',
        'currency',
        'subtotal_amount',
        'discount_amount',
        'shipping_amount',
        'tax_amount',
        'total_amount',
        'email',
        'billing_address_json',
        'shipping_address_json',
        'placed_at',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'status' => 'pending',
        'financial_status' => 'pending',
        'fulfillment_status' => 'unfulfilled',
        'currency' => 'USD',
        'subtotal_amount' => 0,
        'discount_amount' => 0,
        'shipping_amount' => 0,
        'tax_amount' => 0,
        'total_amount' => 0,
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return BelongsTo<Checkout, $this>
     */
    public function checkout(): BelongsTo
    {
        return $this->belongsTo(Checkout::class);
    }

    /**
     * @return BelongsTo<Customer, $this>
     */
    public function customer(): BelongsTo
    {
        return $this->belongsTo(Customer::class);
    }

    /**
     * @return HasMany<OrderLine, $this>
     */
    public function lines(): HasMany
    {
        return $this->hasMany(OrderLine::class);
    }

    /**
     * @return HasMany<Payment, $this>
     */
    public function payments(): HasMany
    {
        return $this->hasMany(Payment::class);
    }

    /**
     * @return HasMany<Refund, $this>
     */
    public function refunds(): HasMany
    {
        return $this->hasMany(Refund::class);
    }

    /**
     * @return HasMany<Fulfillment, $this>
     */
    public function fulfillments(): HasMany
    {
        return $this->hasMany(Fulfillment::class);
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'payment_method' => PaymentMethod::class,
            'status' => OrderStatus::class,
            'financial_status' => FinancialStatus::class,
            'fulfillment_status' => FulfillmentStatus::class,
            'subtotal_amount' => 'integer',
            'discount_amount' => 'integer',
            'shipping_amount' => 'integer',
            'tax_amount' => 'integer',
            'total_amount' => 'integer',
            'billing_address_json' => 'array',
            'shipping_address_json' => 'array',
            'placed_at' => 'datetime',
        ];
    }
}


sed -n '1,260p' database/factories/OrderFactory.php

<?php

namespace Database\Factories;

use App\Enums\FinancialStatus;
use App\Enums\FulfillmentStatus;
use App\Enums\OrderStatus;
use App\Enums\PaymentMethod;
use App\Models\Checkout;
use App\Models\Customer;
use App\Models\Store;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Order>
 */
class OrderFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        $subtotal = fake()->numberBetween(2500, 25000);
        $shipping = fake()->randomElement([0, 499, 799]);
        $tax = 0;

        return [
            'store_id' => Store::factory(),
            'checkout_id' => null,
            'customer_id' => null,
            'order_number' => '#'.fake()->unique()->numberBetween(1001, 9999),
            'payment_method' => PaymentMethod::CreditCard,
            'status' => OrderStatus::Paid,
            'financial_status' => FinancialStatus::Paid,
            'fulfillment_status' => FulfillmentStatus::Unfulfilled,
            'currency' => 'EUR',
            'subtotal_amount' => $subtotal,
            'discount_amount' => 0,
            'shipping_amount' => $shipping,
            'tax_amount' => $tax,
            'total_amount' => $subtotal + $shipping + $tax,
            'email' => fake()->safeEmail(),
            'billing_address_json' => $this->address(),
            'shipping_address_json' => $this->address(),
            'placed_at' => now(),
        ];
    }

    public function forCheckout(?Checkout $checkout = null): static
    {
        return $this->state(fn (array $attributes): array => [
            'checkout_id' => $checkout?->getKey() ?? Checkout::factory(),
        ]);
    }

    public function forCustomer(?Customer $customer = null): static
    {
        return $this->state(fn (array $attributes): array => [
            'customer_id' => $customer?->getKey() ?? Customer::factory(),
        ]);
    }

    public function paid(): static
    {
        return $this->state(fn (array $attributes): array => [
            'status' => OrderStatus::Paid,
            'financial_status' => FinancialStatus::Paid,
        ]);
    }

    public function pending(): static
    {
        return $this->state(fn (array $attributes): array => [
            'status' => OrderStatus::Pending,
            'financial_status' => FinancialStatus::Pending,
        ]);
    }

    public function creditCard(): static
    {
        return $this->state(fn (array $attributes): array => [
            'payment_method' => PaymentMethod::CreditCard,
        ]);
    }

    public function paypal(): static
    {
        return $this->state(fn (array $attributes): array => [
            'payment_method' => PaymentMethod::Paypal,
        ]);
    }

    public function bankTransfer(): static
    {
        return $this->state(fn (array $attributes): array => [
            'payment_method' => PaymentMethod::BankTransfer,
            'status' => OrderStatus::Pending,
            'financial_status' => FinancialStatus::Pending,
        ]);
    }

    public function cancelled(): static
    {
        return $this->state(fn (array $attributes): array => [
            'status' => OrderStatus::Cancelled,
            'financial_status' => FinancialStatus::Voided,
        ]);
    }

    public function partiallyFulfilled(): static
    {
        return $this->state(fn (array $attributes): array => [
            'fulfillment_status' => FulfillmentStatus::Partial,
        ]);
    }

    public function fulfilled(): static
    {
        return $this->state(fn (array $attributes): array => [
            'status' => OrderStatus::Fulfilled,
            'financial_status' => FinancialStatus::Paid,
            'fulfillment_status' => FulfillmentStatus::Fulfilled,
        ]);
    }

    public function refunded(): static
    {
        return $this->state(fn (array $attributes): array => [
            'status' => OrderStatus::Refunded,
            'financial_status' => FinancialStatus::Refunded,
        ]);
    }

    /**
     * @return array<string, string|null>
     */
    private function address(): array
    {
        return [
            'first_name' => fake()->firstName(),
            'last_name' => fake()->lastName(),
            'address1' => fake()->streetAddress(),
            'address2' => null,
            'city' => fake()->city(),
            'province_code' => null,
            'country' => 'DE',
            'postal_code' => fake()->postcode(),
        ];
    }
}


sed -n '1,220p' app/Models/Fulfillment.php

<?php

namespace App\Models;

use App\Enums\FulfillmentShipmentStatus;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Fulfillment extends Model
{
    /** @use HasFactory<\Database\Factories\FulfillmentFactory> */
    use HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'order_id',
        'status',
        'tracking_company',
        'tracking_number',
        'tracking_url',
        'shipped_at',
        'delivered_at',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'status' => 'pending',
    ];

    /**
     * @return BelongsTo<Order, $this>
     */
    public function order(): BelongsTo
    {
        return $this->belongsTo(Order::class);
    }

    /**
     * @return HasMany<FulfillmentLine, $this>
     */
    public function lines(): HasMany
    {
        return $this->hasMany(FulfillmentLine::class);
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'status' => FulfillmentShipmentStatus::class,
            'shipped_at' => 'datetime',
            'delivered_at' => 'datetime',
        ];
    }
}


Output:
Chunk ID: 75289c
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 808
Output:
<?php

namespace App\Models;

use App\Enums\FinancialStatus;
use App\Enums\FulfillmentStatus;
use App\Enums\OrderStatus;
use App\Enums\PaymentMethod;
use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Order extends Model
{
    /** @use HasFactory<\Database\Factories\OrderFactory> */
    use BelongsToStore, HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'checkout_id',
        'customer_id',
        'order_number',
        'payment_method',
        'status',
        'financial_status',
        'fulfillment_status',
        'currency',
        'subtotal_amount',
        'discount_amount',
        'shipping_amount',
        'tax_amount',
        'total_amount',
        'email',
        'billing_address_json',
        'shipping_address_json',
        'placed_at',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'status' => 'pending',
        'financial_status' => 'pending',
        'fulfillment_status' => 'unfulfilled',
        'currency' => 'USD',
        'subtotal_amount' => 0,
        'discount_amount' => 0,
        'shipping_amount' => 0,
        'tax_amount' => 0,
        'total_amount' => 0,
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return BelongsTo<Checkout, $this>
     */
    public function checkout(): BelongsTo
    {
        return $this->belongsTo(Checkout::class);
    }

    /**
     * @return BelongsTo<Customer, $this>
     */
    public function customer(): BelongsTo
    {
        return $this->belongsTo(Customer::class);
    }

    /**
     * @return HasMany<OrderLine, $this>
     */
    public function lines(): HasMany
    {
        return $this->hasMany(OrderLine::class);
    }

    /**
     * @return HasMany<Payment, $this>
     */
    public function payments(): HasMany
    {
        return $this->hasMany(Payment::class);
    }

    /**
     * @return HasMany<Refund, $this>
     */
    public function refunds(): HasMany
    {
        return $this->hasMany(Refund::class);
    }

    /**
     * @return HasMany<Fulfillment, $this>
     */
    public function fulfillments(): HasMany
    {
        return $this->hasMany(Fulfillment::class);
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'payment_method' => PaymentMethod::class,
            'status' => OrderStatus::class,
            'financial_status' => FinancialStatus::class,
            'fulfillment_status' => FulfillmentStatus::class,
            'subtotal_amount' => 'integer',
            'discount_amount' => 'integer',
            'shipping_amount' => 'integer',
            'tax_amount' => 'integer',
            'total_amount' => 'integer',
            'billing_address_json' => 'array',
            'shipping_address_json' => 'array',
            'placed_at' => 'datetime',
        ];
    }
}


Output:
Chunk ID: 6e6dc9
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1152
Output:
<?php

namespace Database\Factories;

use App\Enums\FinancialStatus;
use App\Enums\FulfillmentStatus;
use App\Enums\OrderStatus;
use App\Enums\PaymentMethod;
use App\Models\Checkout;
use App\Models\Customer;
use App\Models\Store;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Order>
 */
class OrderFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        $subtotal = fake()->numberBetween(2500, 25000);
        $shipping = fake()->randomElement([0, 499, 799]);
        $tax = 0;

        return [
            'store_id' => Store::factory(),
            'checkout_id' => null,
            'customer_id' => null,
            'order_number' => '#'.fake()->unique()->numberBetween(1001, 9999),
            'payment_method' => PaymentMethod::CreditCard,
            'status' => OrderStatus::Paid,
            'financial_status' => FinancialStatus::Paid,
            'fulfillment_status' => FulfillmentStatus::Unfulfilled,
            'currency' => 'EUR',
            'subtotal_amount' => $subtotal,
            'discount_amount' => 0,
            'shipping_amount' => $shipping,
            'tax_amount' => $tax,
            'total_amount' => $subtotal + $shipping + $tax,
            'email' => fake()->safeEmail(),
            'billing_address_json' => $this->address(),
            'shipping_address_json' => $this->address(),
            'placed_at' => now(),
        ];
    }

    public function forCheckout(?Checkout $checkout = null): static
    {
        return $this->state(fn (array $attributes): array => [
            'checkout_id' => $checkout?->getKey() ?? Checkout::factory(),
        ]);
    }

    public function forCustomer(?Customer $customer = null): static
    {
        return $this->state(fn (array $attributes): array => [
            'customer_id' => $customer?->getKey() ?? Customer::factory(),
        ]);
    }

    public function paid(): static
    {
        return $this->state(fn (array $attributes): array => [
            'status' => OrderStatus::Paid,
            'financial_status' => FinancialStatus::Paid,
        ]);
    }

    public function pending(): static
    {
        return $this->state(fn (array $attributes): array => [
            'status' => OrderStatus::Pending,
            'financial_status' => FinancialStatus::Pending,
        ]);
    }

    public function creditCard(): static
    {
        return $this->state(fn (array $attributes): array => [
            'payment_method' => PaymentMethod::CreditCard,
        ]);
    }

    public function paypal(): static
    {
        return $this->state(fn (array $attributes): array => [
            'payment_method' => PaymentMethod::Paypal,
        ]);
    }

    public function bankTransfer(): static
    {
        return $this->state(fn (array $attributes): array => [
            'payment_method' => PaymentMethod::BankTransfer,
            'status' => OrderStatus::Pending,
            'financial_status' => FinancialStatus::Pending,
        ]);
    }

    public function cancelled(): static
    {
        return $this->state(fn (array $attributes): array => [
            'status' => OrderStatus::Cancelled,
            'financial_status' => FinancialStatus::Voided,
        ]);
    }

    public function partiallyFulfilled(): static
    {
        return $this->state(fn (array $attributes): array => [
            'fulfillment_status' => FulfillmentStatus::Partial,
        ]);
    }

    public function fulfilled(): static
    {
        return $this->state(fn (array $attributes): array => [
            'status' => OrderStatus::Fulfilled,
            'financial_status' => FinancialStatus::Paid,
            'fulfillment_status' => FulfillmentStatus::Fulfilled,
        ]);
    }

    public function refunded(): static
    {
        return $this->state(fn (array $attributes): array => [
            'status' => OrderStatus::Refunded,
            'financial_status' => FinancialStatus::Refunded,
        ]);
    }

    /**
     * @return array<string, string|null>
     */
    private function address(): array
    {
        return [
            'first_name' => fake()->firstName(),
            'last_name' => fake()->lastName(),
            'address1' => fake()->streetAddress(),
            'address2' => null,
            'city' => fake()->city(),
            'province_code' => null,
            'country' => 'DE',
            'postal_code' => fake()->postcode(),
        ];
    }
}


Output:
Chunk ID: 9fed0a
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 337
Output:
<?php

namespace App\Models;

use App\Enums\FulfillmentShipmentStatus;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Fulfillment extends Model
{
    /** @use HasFactory<\Database\Factories\FulfillmentFactory> */
    use HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'order_id',
        'status',
        'tracking_company',
        'tracking_number',
        'tracking_url',
        'shipped_at',
        'delivered_at',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'status' => 'pending',
    ];

    /**
     * @return BelongsTo<Order, $this>
     */
    public function order(): BelongsTo
    {
        return $this->belongsTo(Order::class);
    }

    /**
     * @return HasMany<FulfillmentLine, $this>
     */
    public function lines(): HasMany
    {
        return $this->hasMany(FulfillmentLine::class);
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'status' => FulfillmentShipmentStatus::class,
            'shipped_at' => 'datetime',
            'delivered_at' => 'datetime',
        ];
    }
}


sed -n '1,220p' database/migrations/*create_orders* 2>/dev/null

<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('orders', function (Blueprint $table) {
            $table->id();
            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
            $table->foreignId('checkout_id')->nullable()->constrained()->nullOnDelete();
            $table->foreignId('customer_id')->nullable()->constrained()->nullOnDelete();
            $table->string('order_number');
            $table->enum('payment_method', ['credit_card', 'paypal', 'bank_transfer']);
            $table->enum('status', ['pending', 'paid', 'fulfilled', 'cancelled', 'refunded'])->default('pending');
            $table->enum('financial_status', ['pending', 'authorized', 'paid', 'partially_refunded', 'refunded', 'voided'])->default('pending');
            $table->enum('fulfillment_status', ['unfulfilled', 'partial', 'fulfilled'])->default('unfulfilled');
            $table->string('currency', 3)->default('USD');
            $table->integer('subtotal_amount')->default(0);
            $table->integer('discount_amount')->default(0);
            $table->integer('shipping_amount')->default(0);
            $table->integer('tax_amount')->default(0);
            $table->integer('total_amount')->default(0);
            $table->string('email')->nullable();
            $table->text('billing_address_json')->nullable();
            $table->text('shipping_address_json')->nullable();
            $table->timestamp('placed_at')->nullable();
            $table->timestamps();

            $table->unique(['store_id', 'order_number']);
            $table->unique('checkout_id');
            $table->index('store_id');
            $table->index('customer_id');
            $table->index(['store_id', 'status']);
            $table->index(['store_id', 'financial_status']);
            $table->index(['store_id', 'fulfillment_status']);
            $table->index(['store_id', 'placed_at']);
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('orders');
    }
};


Output:
Chunk ID: f0dd50
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 571
Output:
<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('orders', function (Blueprint $table) {
            $table->id();
            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
            $table->foreignId('checkout_id')->nullable()->constrained()->nullOnDelete();
            $table->foreignId('customer_id')->nullable()->constrained()->nullOnDelete();
            $table->string('order_number');
            $table->enum('payment_method', ['credit_card', 'paypal', 'bank_transfer']);
            $table->enum('status', ['pending', 'paid', 'fulfilled', 'cancelled', 'refunded'])->default('pending');
            $table->enum('financial_status', ['pending', 'authorized', 'paid', 'partially_refunded', 'refunded', 'voided'])->default('pending');
            $table->enum('fulfillment_status', ['unfulfilled', 'partial', 'fulfilled'])->default('unfulfilled');
            $table->string('currency', 3)->default('USD');
            $table->integer('subtotal_amount')->default(0);
            $table->integer('discount_amount')->default(0);
            $table->integer('shipping_amount')->default(0);
            $table->integer('tax_amount')->default(0);
            $table->integer('total_amount')->default(0);
            $table->string('email')->nullable();
            $table->text('billing_address_json')->nullable();
            $table->text('shipping_address_json')->nullable();
            $table->timestamp('placed_at')->nullable();
            $table->timestamps();

            $table->unique(['store_id', 'order_number']);
            $table->unique('checkout_id');
            $table->index('store_id');
            $table->index('customer_id');
            $table->index(['store_id', 'status']);
            $table->index(['store_id', 'financial_status']);
            $table->index(['store_id', 'fulfillment_status']);
            $table->index(['store_id', 'placed_at']);
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('orders');
    }
};


sed -n '1,260p' tests/Feature/Api/AdminOrderApiTest.php

<?php

use App\Enums\FinancialStatus;
use App\Enums\FulfillmentStatus;
use App\Enums\PaymentStatus;
use App\Models\Fulfillment;
use App\Models\InventoryItem;
use App\Models\Order;
use App\Models\OrderLine;
use App\Models\Payment;
use App\Models\Product;
use App\Models\ProductVariant;
use App\Models\Store;
use App\Models\User;
use App\Services\WebhookService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

function adminOrderApiStore(): Store
{
    return Store::query()->where('handle', 'acme-fashion')->firstOrFail();
}

function adminOrderApiUser(): User
{
    return User::query()->where('email', 'admin@acme.test')->firstOrFail();
}

/**
 * @param  list<string>  $abilities
 * @return array{token: \App\Models\OauthToken, plain_text: string}
 */
function adminOrderApiToken(Store $store, array $abilities): array
{
    return app(WebhookService::class)->createApiToken($store, 'Order integration', $abilities);
}

/**
 * @return array{0: Order, 1: OrderLine, 2: ProductVariant}
 */
function adminOrderApiOrder(Store $store, array $orderAttributes = [], int $quantity = 2, int $unitPrice = 2500): array
{
    $product = Product::factory()
        ->withDefaultVariant($unitPrice)
        ->create(['store_id' => $store->getKey()]);
    $variant = ProductVariant::withoutGlobalScopes()
        ->where('product_id', $product->getKey())
        ->firstOrFail();

    InventoryItem::withoutGlobalScopes()
        ->where('variant_id', $variant->getKey())
        ->update([
            'quantity_on_hand' => 10,
            'quantity_reserved' => 0,
        ]);

    $total = $quantity * $unitPrice;
    $order = Order::factory()->paid()->create(array_merge([
        'store_id' => $store->getKey(),
        'subtotal_amount' => $total,
        'shipping_amount' => 0,
        'tax_amount' => 0,
        'total_amount' => $total,
        'email' => 'buyer@example.test',
    ], $orderAttributes));
    $line = OrderLine::factory()->create([
        'order_id' => $order->getKey(),
        'product_id' => $product->getKey(),
        'variant_id' => $variant->getKey(),
        'title_snapshot' => 'Admin API Product',
        'sku_snapshot' => 'ADMIN-API-001',
        'quantity' => $quantity,
        'unit_price_amount' => $unitPrice,
        'total_amount' => $total,
    ]);

    Payment::factory()->create([
        'order_id' => $order->getKey(),
        'status' => $order->financial_status === FinancialStatus::Pending ? PaymentStatus::Pending : PaymentStatus::Captured,
        'amount' => $total,
        'currency' => $order->currency,
    ]);

    return [$order, $line, $variant];
}

test('admin order api lists and shows store scoped orders', function (): void {
    $store = adminOrderApiStore();
    [$order] = adminOrderApiOrder($store, [
        'order_number' => '#8001',
        'email' => 'alpha@example.test',
    ]);
    $otherStore = Store::factory()->create();
    adminOrderApiOrder($otherStore, [
        'order_number' => '#9001',
        'email' => 'other@example.test',
    ]);

    $this->getJson("/api/admin/v1/stores/{$store->getKey()}/orders")
        ->assertUnauthorized();

    $this->actingAs(adminOrderApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders?query=alpha")
        ->assertOk()
        ->assertJsonPath('data.0.order_number', '#8001')
        ->assertJsonMissing(['order_number' => '#9001']);

    $this->actingAs(adminOrderApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}")
        ->assertOk()
        ->assertJsonPath('data.order_number', '#8001')
        ->assertJsonPath('data.lines.0.title', 'Admin API Product');
});

test('admin order api creates refunds and fulfillments', function (): void {
    $store = adminOrderApiStore();
    [$order, $line] = adminOrderApiOrder($store, quantity: 2, unitPrice: 2500);
    $user = adminOrderApiUser();

    $this->actingAs($user)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/refunds", [
            'amount' => 1000,
            'reason' => 'Customer return',
        ])
        ->assertCreated()
        ->assertJsonPath('data.amount', 1000)
        ->assertJsonPath('data.status', 'processed');

    expect($order->refresh()->financial_status)->toBe(FinancialStatus::PartiallyRefunded);

    $this->actingAs($user)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/fulfillments", [
            'line_items' => [
                ['order_line_id' => $line->getKey(), 'quantity' => 1],
            ],
            'tracking_company' => 'DHL',
            'tracking_number' => 'DHL123',
        ])
        ->assertCreated()
        ->assertJsonPath('data.status', 'pending')
        ->assertJsonPath('data.lines.0.quantity', 1);

    $fulfillment = Fulfillment::query()->where('order_id', $order->getKey())->firstOrFail();

    expect($order->refresh()->fulfillment_status)->toBe(FulfillmentStatus::Partial)
        ->and($fulfillment->tracking_number)->toBe('DHL123');
});

test('admin order api rejects orders outside the requested store', function (): void {
    $store = adminOrderApiStore();
    $otherStore = Store::factory()->create();
    [$otherOrder] = adminOrderApiOrder($otherStore, [
        'order_number' => '#9001',
    ]);

    $this->actingAs(adminOrderApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$otherOrder->getKey()}")
        ->assertNotFound();
});

test('admin order api accepts scoped bearer tokens', function (): void {
    $store = adminOrderApiStore();
    adminOrderApiOrder($store, [
        'order_number' => '#8101',
        'email' => 'token@example.test',
    ]);
    $result = adminOrderApiToken($store, ['read-orders']);

    $this->withToken($result['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders?query=token")
        ->assertOk()
        ->assertJsonPath('data.0.order_number', '#8101');

    expect($result['token']->refresh()->last_used_at)->not->toBeNull();
});

test('admin order api enforces token store scope and abilities', function (): void {
    $store = adminOrderApiStore();
    [$order] = adminOrderApiOrder($store);
    $readOnly = adminOrderApiToken($store, ['read-orders']);
    $writeToken = adminOrderApiToken($store, ['write-orders']);
    $otherStore = Store::factory()->create();
    $otherStoreToken = adminOrderApiToken($otherStore, ['read-orders']);

    $this->withToken($readOnly['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/refunds", [
            'amount' => 500,
            'reason' => 'Read-only token',
        ])
        ->assertForbidden();

    $this->withToken($otherStoreToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders")
        ->assertForbidden();

    $this->withToken($writeToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/refunds", [
            'amount' => 500,
            'reason' => 'Write token',
        ])
        ->assertCreated()
        ->assertJsonPath('data.amount', 500);
});

test('admin order api rejects expired bearer tokens', function (): void {
    $store = adminOrderApiStore();
    adminOrderApiOrder($store);
    $result = adminOrderApiToken($store, ['read-orders']);
    $result['token']->forceFill(['expires_at' => now()->subMinute()])->save();

    $this->withToken($result['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders")
        ->assertUnauthorized();
});


Output:
Chunk ID: 129d8a
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1936
Output:
<?php

use App\Enums\FinancialStatus;
use App\Enums\FulfillmentStatus;
use App\Enums\PaymentStatus;
use App\Models\Fulfillment;
use App\Models\InventoryItem;
use App\Models\Order;
use App\Models\OrderLine;
use App\Models\Payment;
use App\Models\Product;
use App\Models\ProductVariant;
use App\Models\Store;
use App\Models\User;
use App\Services\WebhookService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

function adminOrderApiStore(): Store
{
    return Store::query()->where('handle', 'acme-fashion')->firstOrFail();
}

function adminOrderApiUser(): User
{
    return User::query()->where('email', 'admin@acme.test')->firstOrFail();
}

/**
 * @param  list<string>  $abilities
 * @return array{token: \App\Models\OauthToken, plain_text: string}
 */
function adminOrderApiToken(Store $store, array $abilities): array
{
    return app(WebhookService::class)->createApiToken($store, 'Order integration', $abilities);
}

/**
 * @return array{0: Order, 1: OrderLine, 2: ProductVariant}
 */
function adminOrderApiOrder(Store $store, array $orderAttributes = [], int $quantity = 2, int $unitPrice = 2500): array
{
    $product = Product::factory()
        ->withDefaultVariant($unitPrice)
        ->create(['store_id' => $store->getKey()]);
    $variant = ProductVariant::withoutGlobalScopes()
        ->where('product_id', $product->getKey())
        ->firstOrFail();

    InventoryItem::withoutGlobalScopes()
        ->where('variant_id', $variant->getKey())
        ->update([
            'quantity_on_hand' => 10,
            'quantity_reserved' => 0,
        ]);

    $total = $quantity * $unitPrice;
    $order = Order::factory()->paid()->create(array_merge([
        'store_id' => $store->getKey(),
        'subtotal_amount' => $total,
        'shipping_amount' => 0,
        'tax_amount' => 0,
        'total_amount' => $total,
        'email' => 'buyer@example.test',
    ], $orderAttributes));
    $line = OrderLine::factory()->create([
        'order_id' => $order->getKey(),
        'product_id' => $product->getKey(),
        'variant_id' => $variant->getKey(),
        'title_snapshot' => 'Admin API Product',
        'sku_snapshot' => 'ADMIN-API-001',
        'quantity' => $quantity,
        'unit_price_amount' => $unitPrice,
        'total_amount' => $total,
    ]);

    Payment::factory()->create([
        'order_id' => $order->getKey(),
        'status' => $order->financial_status === FinancialStatus::Pending ? PaymentStatus::Pending : PaymentStatus::Captured,
        'amount' => $total,
        'currency' => $order->currency,
    ]);

    return [$order, $line, $variant];
}

test('admin order api lists and shows store scoped orders', function (): void {
    $store = adminOrderApiStore();
    [$order] = adminOrderApiOrder($store, [
        'order_number' => '#8001',
        'email' => 'alpha@example.test',
    ]);
    $otherStore = Store::factory()->create();
    adminOrderApiOrder($otherStore, [
        'order_number' => '#9001',
        'email' => 'other@example.test',
    ]);

    $this->getJson("/api/admin/v1/stores/{$store->getKey()}/orders")
        ->assertUnauthorized();

    $this->actingAs(adminOrderApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders?query=alpha")
        ->assertOk()
        ->assertJsonPath('data.0.order_number', '#8001')
        ->assertJsonMissing(['order_number' => '#9001']);

    $this->actingAs(adminOrderApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}")
        ->assertOk()
        ->assertJsonPath('data.order_number', '#8001')
        ->assertJsonPath('data.lines.0.title', 'Admin API Product');
});

test('admin order api creates refunds and fulfillments', function (): void {
    $store = adminOrderApiStore();
    [$order, $line] = adminOrderApiOrder($store, quantity: 2, unitPrice: 2500);
    $user = adminOrderApiUser();

    $this->actingAs($user)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/refunds", [
            'amount' => 1000,
            'reason' => 'Customer return',
        ])
        ->assertCreated()
        ->assertJsonPath('data.amount', 1000)
        ->assertJsonPath('data.status', 'processed');

    expect($order->refresh()->financial_status)->toBe(FinancialStatus::PartiallyRefunded);

    $this->actingAs($user)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/fulfillments", [
            'line_items' => [
                ['order_line_id' => $line->getKey(), 'quantity' => 1],
            ],
            'tracking_company' => 'DHL',
            'tracking_number' => 'DHL123',
        ])
        ->assertCreated()
        ->assertJsonPath('data.status', 'pending')
        ->assertJsonPath('data.lines.0.quantity', 1);

    $fulfillment = Fulfillment::query()->where('order_id', $order->getKey())->firstOrFail();

    expect($order->refresh()->fulfillment_status)->toBe(FulfillmentStatus::Partial)
        ->and($fulfillment->tracking_number)->toBe('DHL123');
});

test('admin order api rejects orders outside the requested store', function (): void {
    $store = adminOrderApiStore();
    $otherStore = Store::factory()->create();
    [$otherOrder] = adminOrderApiOrder($otherStore, [
        'order_number' => '#9001',
    ]);

    $this->actingAs(adminOrderApiUser())
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$otherOrder->getKey()}")
        ->assertNotFound();
});

test('admin order api accepts scoped bearer tokens', function (): void {
    $store = adminOrderApiStore();
    adminOrderApiOrder($store, [
        'order_number' => '#8101',
        'email' => 'token@example.test',
    ]);
    $result = adminOrderApiToken($store, ['read-orders']);

    $this->withToken($result['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders?query=token")
        ->assertOk()
        ->assertJsonPath('data.0.order_number', '#8101');

    expect($result['token']->refresh()->last_used_at)->not->toBeNull();
});

test('admin order api enforces token store scope and abilities', function (): void {
    $store = adminOrderApiStore();
    [$order] = adminOrderApiOrder($store);
    $readOnly = adminOrderApiToken($store, ['read-orders']);
    $writeToken = adminOrderApiToken($store, ['write-orders']);
    $otherStore = Store::factory()->create();
    $otherStoreToken = adminOrderApiToken($otherStore, ['read-orders']);

    $this->withToken($readOnly['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/refunds", [
            'amount' => 500,
            'reason' => 'Read-only token',
        ])
        ->assertForbidden();

    $this->withToken($otherStoreToken['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders")
        ->assertForbidden();

    $this->withToken($writeToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/orders/{$order->getKey()}/refunds", [
            'amount' => 500,
            'reason' => 'Write token',
        ])
        ->assertCreated()
        ->assertJsonPath('data.amount', 500);
});

test('admin order api rejects expired bearer tokens', function (): void {
    $store = adminOrderApiStore();
    adminOrderApiOrder($store);
    $result = adminOrderApiToken($store, ['read-orders']);
    $result['token']->forceFill(['expires_at' => now()->subMinute()])->save();

    $this->withToken($result['plain_text'])
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/orders")
        ->assertUnauthorized();
});


rg -n "enum .*Status|case Completed|ExportStatus" app/Enums app/Models database/factories

rg -n 'enum .*Status|case Completed|ExportStatus' app/Enums app/Models database/factories

Question: enum .*Status|case Completed|ExportStatus

app/Enums/PaymentStatus.php:5:enum PaymentStatus: string
app/Enums/ThemeStatus.php:5:enum ThemeStatus: string
app/Enums/WebhookDeliveryStatus.php:5:enum WebhookDeliveryStatus: string
app/Enums/StoreStatus.php:5:enum StoreStatus: string
app/Enums/FulfillmentStatus.php:5:enum FulfillmentStatus: string
app/Enums/AppInstallationStatus.php:5:enum AppInstallationStatus: string
app/Enums/ProductStatus.php:5:enum ProductStatus: string
app/Enums/CheckoutStatus.php:5:enum CheckoutStatus: string
app/Enums/CheckoutStatus.php:11:    case Completed = 'completed';
app/Enums/FinancialStatus.php:5:enum FinancialStatus: string
app/Enums/AppStatus.php:5:enum AppStatus: string
app/Enums/MediaStatus.php:5:enum MediaStatus: string
app/Enums/CollectionStatus.php:5:enum CollectionStatus: string
app/Enums/PageStatus.php:5:enum PageStatus: string
app/Enums/VariantStatus.php:5:enum VariantStatus: string
app/Enums/DiscountStatus.php:5:enum DiscountStatus: string
app/Enums/OrderStatus.php:5:enum OrderStatus: string
app/Enums/RefundStatus.php:5:enum RefundStatus: string
app/Enums/CartStatus.php:5:enum CartStatus: string
app/Enums/FulfillmentShipmentStatus.php:5:enum FulfillmentShipmentStatus: string
app/Enums/WebhookSubscriptionStatus.php:5:enum WebhookSubscriptionStatus: string


Output:
Chunk ID: e26493
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 318
Output:
app/Enums/PaymentStatus.php:5:enum PaymentStatus: string
app/Enums/ThemeStatus.php:5:enum ThemeStatus: string
app/Enums/WebhookDeliveryStatus.php:5:enum WebhookDeliveryStatus: string
app/Enums/StoreStatus.php:5:enum StoreStatus: string
app/Enums/FulfillmentStatus.php:5:enum FulfillmentStatus: string
app/Enums/AppInstallationStatus.php:5:enum AppInstallationStatus: string
app/Enums/ProductStatus.php:5:enum ProductStatus: string
app/Enums/CheckoutStatus.php:5:enum CheckoutStatus: string
app/Enums/CheckoutStatus.php:11:    case Completed = 'completed';
app/Enums/FinancialStatus.php:5:enum FinancialStatus: string
app/Enums/AppStatus.php:5:enum AppStatus: string
app/Enums/MediaStatus.php:5:enum MediaStatus: string
app/Enums/CollectionStatus.php:5:enum CollectionStatus: string
app/Enums/PageStatus.php:5:enum PageStatus: string
app/Enums/VariantStatus.php:5:enum VariantStatus: string
app/Enums/DiscountStatus.php:5:enum DiscountStatus: string
app/Enums/OrderStatus.php:5:enum OrderStatus: string
app/Enums/RefundStatus.php:5:enum RefundStatus: string
app/Enums/CartStatus.php:5:enum CartStatus: string
app/Enums/FulfillmentShipmentStatus.php:5:enum FulfillmentShipmentStatus: string
app/Enums/WebhookSubscriptionStatus.php:5:enum WebhookSubscriptionStatus: string


The spec’s export endpoint expects async status tracking, but the schema has no export table yet. I’m going to add a small durable `data_exports` table/model and generate CSV synchronously into local storage for this self-contained app, returning `202` with a completed export row and a data URL from the status endpoint.


php artisan make:enum ExportStatus --no-interaction


php artisan make:model DataExport --migration --factory --no-interaction


php artisan make:class Services/OrderExportService --no-interaction


php artisan make:controller Api/Admin/V1/OrderExportController --no-interaction


php artisan make:resource Admin/V1/DataExportResource --no-interaction


php artisan make:test --pest Api/AdminOrderExportApiTest --no-interaction


Output:
Chunk ID: 02ca08
Wall time: 0.2330 seconds
Process exited with code 0
Original token count: 18
Output:

   INFO  Enum [app/Enums/ExportStatus.php] created successfully.


Output:
Chunk ID: 356811
Wall time: 0.2248 seconds
Process exited with code 0
Original token count: 67
Output:

   INFO  Model [app/Models/DataExport.php] created successfully.

   INFO  Factory [database/factories/DataExportFactory.php] created successfully.

   INFO  Migration [database/migrations/2026_05_04_074802_create_data_exports_table.php] created successfully.


Output:
Chunk ID: cfa809
Wall time: 0.2114 seconds
Process exited with code 0
Original token count: 20
Output:

   INFO  Class [app/Services/OrderExportService.php] created successfully.


Output:
Chunk ID: d90c3f
Wall time: 0.2013 seconds
Process exited with code 0
Original token count: 27
Output:

   INFO  Controller [app/Http/Controllers/Api/Admin/V1/OrderExportController.php] created successfully.


Output:
Chunk ID: 6f2395
Wall time: 0.2003 seconds
Process exited with code 0
Original token count: 25
Output:

   INFO  Resource [app/Http/Resources/Admin/V1/DataExportResource.php] created successfully.


Output:
Chunk ID: 0b936e
Wall time: 0.1998 seconds
Process exited with code 0
Original token count: 22
Output:

   INFO  Test [tests/Feature/Api/AdminOrderExportApiTest.php] created successfully.


php artisan make:enum ExportStatus --no-interaction

INFO  Enum [app/Enums/ExportStatus.php] created successfully.


php artisan make:class Services/OrderExportService --no-interaction

INFO  Class [app/Services/OrderExportService.php] created successfully.


php artisan make:resource Admin/V1/DataExportResource --no-interaction

INFO  Resource [app/Http/Resources/Admin/V1/DataExportResource.php] created successfully.


php artisan make:controller Api/Admin/V1/OrderExportController --no-interaction

INFO  Controller [app/Http/Controllers/Api/Admin/V1/OrderExportController.php] created successfully.


php artisan make:test --pest Api/AdminOrderExportApiTest --no-interaction

INFO  Test [tests/Feature/Api/AdminOrderExportApiTest.php] created successfully.


php artisan make:model DataExport --migration --factory --no-interaction

   INFO  Model [app/Models/DataExport.php] created successfully.

   INFO  Factory [database/factories/DataExportFactory.php] created successfully.

   INFO  Migration [database/migrations/2026_05_04_074802_create_data_exports_table.php] created successfully.


sed -n '1,120p' app/Enums/ExportStatus.php


sed -n '1,220p' app/Models/DataExport.php


sed -n '1,220p' database/factories/DataExportFactory.php


sed -n '1,220p' database/migrations/2026_05_04_074802_create_data_exports_table.php


sed -n '1,120p' app/Services/OrderExportService.php


sed -n '1,120p' app/Http/Controllers/Api/Admin/V1/OrderExportController.php


sed -n '1,120p' app/Enums/ExportStatus.php

<?php

namespace App\Enums;

enum ExportStatus
{
    //
}


sed -n '1,220p' app/Models/DataExport.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class DataExport extends Model
{
    /** @use HasFactory<\Database\Factories\DataExportFactory> */
    use HasFactory;
}


Output:
Chunk ID: da6924
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 15
Output:
<?php

namespace App\Enums;

enum ExportStatus
{
    //
}


Output:
Chunk ID: 91463e
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 62
Output:
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class DataExport extends Model
{
    /** @use HasFactory<\Database\Factories\DataExportFactory> */
    use HasFactory;
}


sed -n '1,220p' database/factories/DataExportFactory.php

<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\DataExport>
 */
class DataExportFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            //
        ];
    }
}


sed -n '1,220p' database/migrations/2026_05_04_074802_create_data_exports_table.php

<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('data_exports', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('data_exports');
    }
};


Output:
Chunk ID: 11ecbf
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 106
Output:
<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\DataExport>
 */
class DataExportFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            //
        ];
    }
}


Output:
Chunk ID: b68021
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 136
Output:
<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('data_exports', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('data_exports');
    }
};


sed -n '1,120p' app/Services/OrderExportService.php

<?php

namespace App\Services;

class OrderExportService
{
    /**
     * Create a new class instance.
     */
    public function __construct()
    {
        //
    }
}


sed -n '1,120p' app/Http/Controllers/Api/Admin/V1/OrderExportController.php

<?php

namespace App\Http\Controllers\Api\Admin\V1;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class OrderExportController extends Controller
{
    //
}


Output:
Chunk ID: 5f2b5c
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 43
Output:
<?php

namespace App\Services;

class OrderExportService
{
    /**
     * Create a new class instance.
     */
    public function __construct()
    {
        //
    }
}


Output:
Chunk ID: 5ebc81
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 45
Output:
<?php

namespace App\Http\Controllers\Api\Admin\V1;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class OrderExportController extends Controller
{
    //
}


Input:
*** Begin Patch
*** Update File: app/Enums/ExportStatus.php
@@
-enum ExportStatus
+enum ExportStatus: string
 {
-    //
+    case Queued = 'queued';
+    case Processing = 'processing';
+    case Completed = 'completed';
+    case Failed = 'failed';
 }
*** End Patch


Success. Updated the following files:
M app/Enums/ExportStatus.php

@@ -4,5 +4,8 @@

-enum ExportStatus
+enum ExportStatus: string
 {
-    //
+    case Queued = 'queued';
+    case Processing = 'processing';
+    case Completed = 'completed';
+    case Failed = 'failed';
 }


Output: {"output":"Success. Updated the following files:\nM app/Enums/ExportStatus.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Update File: database/migrations/2026_05_04_074802_create_data_exports_table.php
@@
     public function up(): void
     {
         Schema::create('data_exports', function (Blueprint $table) {
             $table->id();
+            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
+            $table->string('type')->default('orders');
+            $table->string('format', 20)->default('csv');
+            $table->enum('status', ['queued', 'processing', 'completed', 'failed'])->default('queued');
+            $table->text('filters_json')->default('{}');
+            $table->unsignedInteger('row_count')->default(0);
+            $table->string('storage_key')->nullable();
+            $table->text('error_message')->nullable();
+            $table->timestamp('download_expires_at')->nullable();
+            $table->timestamp('completed_at')->nullable();
+            $table->timestamp('failed_at')->nullable();
             $table->timestamps();
+
+            $table->index('store_id');
+            $table->index(['store_id', 'status']);
         });
     }
*** End Patch


Success. Updated the following files:
M database/migrations/2026_05_04_074802_create_data_exports_table.php

@@ -15,3 +15,17 @@
             $table->id();
+            $table->foreignId('store_id')->constrained()->cascadeOnDelete();
+            $table->string('type')->default('orders');
+            $table->string('format', 20)->default('csv');
+            $table->enum('status', ['queued', 'processing', 'completed', 'failed'])->default('queued');
+            $table->text('filters_json')->default('{}');
+            $table->unsignedInteger('row_count')->default(0);
+            $table->string('storage_key')->nullable();
+            $table->text('error_message')->nullable();
+            $table->timestamp('download_expires_at')->nullable();
+            $table->timestamp('completed_at')->nullable();
+            $table->timestamp('failed_at')->nullable();
             $table->timestamps();
+
+            $table->index('store_id');
+            $table->index(['store_id', 'status']);
         });


Output: {"output":"Success. Updated the following files:\nM database/migrations/2026_05_04_074802_create_data_exports_table.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Update File: app/Models/DataExport.php
@@
 namespace App\Models;

+use App\Enums\ExportStatus;
 use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;

 class DataExport extends Model
 {
     /** @use HasFactory<\Database\Factories\DataExportFactory> */
     use HasFactory;
+
+    /**
+     * @var list<string>
+     */
+    protected $fillable = [
+        'store_id',
+        'type',
+        'format',
+        'status',
+        'filters_json',
+        'row_count',
+        'storage_key',
+        'error_message',
+        'download_expires_at',
+        'completed_at',
+        'failed_at',
+    ];
+
+    /**
+     * @var array<string, mixed>
+     */
+    protected $attributes = [
+        'type' => 'orders',
+        'format' => 'csv',
+        'status' => 'queued',
+        'filters_json' => '{}',
+        'row_count' => 0,
+    ];
+
+    /**
+     * @return BelongsTo<Store, $this>
+     */
+    public function store(): BelongsTo
+    {
+        return $this->belongsTo(Store::class);
+    }
+
+    /**
+     * @return array<string, string>
+     */
+    protected function casts(): array
+    {
+        return [
+            'status' => ExportStatus::class,
+            'filters_json' => 'array',
+            'row_count' => 'integer',
+            'download_expires_at' => 'datetime',
+            'completed_at' => 'datetime',
+            'failed_at' => 'datetime',
+        ];
+    }
 }
*** End Patch


Success. Updated the following files:
M app/Models/DataExport.php

@@ -4,4 +4,6 @@

+use App\Enums\ExportStatus;
 use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;

@@ -11,2 +13,53 @@
     use HasFactory;
+
+    /**
+     * @var list<string>
+     */
+    protected $fillable = [
+        'store_id',
+        'type',
+        'format',
+        'status',
+        'filters_json',
+        'row_count',
+        'storage_key',
+        'error_message',
+        'download_expires_at',
+        'completed_at',
+        'failed_at',
+    ];
+
+    /**
+     * @var array<string, mixed>
+     */
+    protected $attributes = [
+        'type' => 'orders',
+        'format' => 'csv',
+        'status' => 'queued',
+        'filters_json' => '{}',
+        'row_count' => 0,
+    ];
+
+    /**
+     * @return BelongsTo<Store, $this>
+     */
+    public function store(): BelongsTo
+    {
+        return $this->belongsTo(Store::class);
+    }
+
+    /**
+     * @return array<string, string>
+     */
+    protected function casts(): array
+    {
+        return [
+            'status' => ExportStatus::class,
+            'filters_json' => 'array',
+            'row_count' => 'integer',
+            'download_expires_at' => 'datetime',
+            'completed_at' => 'datetime',
+            'failed_at' => 'datetime',
+        ];
+    }
 }


Output: {"output":"Success. Updated the following files:\nM app/Models/DataExport.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Update File: database/factories/DataExportFactory.php
@@
 namespace Database\Factories;

+use App\Enums\ExportStatus;
+use App\Models\Store;
 use Illuminate\Database\Eloquent\Factories\Factory;
@@
     public function definition(): array
     {
         return [
-            //
+            'store_id' => Store::factory(),
+            'type' => 'orders',
+            'format' => 'csv',
+            'status' => ExportStatus::Completed,
+            'filters_json' => [],
+            'row_count' => fake()->numberBetween(1, 25),
+            'storage_key' => 'exports/orders/'.fake()->uuid().'.csv',
+            'download_expires_at' => now()->addHour(),
+            'completed_at' => now(),
         ];
     }
 }
*** End Patch


Success. Updated the following files:
M database/factories/DataExportFactory.php

@@ -4,2 +4,4 @@

+use App\Enums\ExportStatus;
+use App\Models\Store;
 use Illuminate\Database\Eloquent\Factories\Factory;
@@ -19,3 +21,11 @@
         return [
-            //
+            'store_id' => Store::factory(),
+            'type' => 'orders',
+            'format' => 'csv',
+            'status' => ExportStatus::Completed,
+            'filters_json' => [],
+            'row_count' => fake()->numberBetween(1, 25),
+            'storage_key' => 'exports/orders/'.fake()->uuid().'.csv',
+            'download_expires_at' => now()->addHour(),
+            'completed_at' => now(),
         ];


Output: {"output":"Success. Updated the following files:\nM database/factories/DataExportFactory.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Update File: app/Services/OrderExportService.php
@@
 namespace App\Services;

+use App\Enums\ExportStatus;
+use App\Models\DataExport;
+use App\Models\Order;
+use App\Models\Store;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Support\Facades\Storage;
+use Throwable;
+
 class OrderExportService
 {
     /**
-     * Create a new class instance.
+     * @param  array<string, mixed>  $filters
      */
-    public function __construct()
+    public function create(Store $store, array $filters): DataExport
     {
-        //
+        $export = DataExport::query()->create([
+            'store_id' => $store->getKey(),
+            'type' => 'orders',
+            'format' => 'csv',
+            'status' => ExportStatus::Queued,
+            'filters_json' => $filters,
+        ]);
+
+        try {
+            $export->forceFill(['status' => ExportStatus::Processing])->save();
+
+            [$csv, $rowCount] = $this->csv($store, $filters);
+            $storageKey = "exports/orders/{$export->getKey()}.csv";
+
+            Storage::disk('local')->put($storageKey, $csv);
+
+            $export->forceFill([
+                'status' => ExportStatus::Completed,
+                'row_count' => $rowCount,
+                'storage_key' => $storageKey,
+                'download_expires_at' => now()->addHour(),
+                'completed_at' => now(),
+            ])->save();
+        } catch (Throwable $exception) {
+            $export->forceFill([
+                'status' => ExportStatus::Failed,
+                'error_message' => $exception->getMessage(),
+                'failed_at' => now(),
+            ])->save();
+
+            throw $exception;
+        }
+
+        return $export->refresh();
+    }
+
+    /**
+     * @param  array<string, mixed>  $filters
+     * @return array{0: string, 1: int}
+     */
+    private function csv(Store $store, array $filters): array
+    {
+        $handle = fopen('php://temp', 'r+');
+
+        fputcsv($handle, [
+            'order_number',
+            'created_at',
+            'status',
+            'financial_status',
+            'fulfillment_status',
+            'customer_email',
+            'customer_name',
+            'subtotal_amount',
+            'discount_amount',
+            'shipping_amount',
+            'tax_amount',
+            'total_amount',
+            'currency',
+            'shipping_method',
+            'tracking_number',
+        ]);
+
+        $rowCount = 0;
+
+        $this->query($store, $filters)
+            ->with(['customer', 'fulfillments'])
+            ->orderBy('id')
+            ->each(function (Order $order) use ($handle, &$rowCount): void {
+                fputcsv($handle, [
+                    $order->order_number,
+                    $order->created_at?->toIso8601String(),
+                    $order->status?->value,
+                    $order->financial_status?->value,
+                    $order->fulfillment_status?->value,
+                    $order->email,
+                    $order->customer?->name,
+                    $order->subtotal_amount,
+                    $order->discount_amount,
+                    $order->shipping_amount,
+                    $order->tax_amount,
+                    $order->total_amount,
+                    $order->currency,
+                    '',
+                    $order->fulfillments->first()?->tracking_number,
+                ]);
+
+                $rowCount++;
+            });
+
+        rewind($handle);
+
+        return [(string) stream_get_contents($handle), $rowCount];
+    }
+
+    /**
+     * @param  array<string, mixed>  $filters
+     * @return Builder<Order>
+     */
+    private function query(Store $store, array $filters): Builder
+    {
+        return Order::withoutGlobalScopes()
+            ->where('store_id', $store->getKey())
+            ->when(data_get($filters, 'status'), fn (Builder $query, string $status) => $query->where('status', $status))
+            ->when(data_get($filters, 'financial_status'), fn (Builder $query, string $status) => $query->where('financial_status', $status))
+            ->when(data_get($filters, 'fulfillment_status'), fn (Builder $query, string $status) => $query->where('fulfillment_status', $status))
+            ->when(data_get($filters, 'created_after'), fn (Builder $query, string $date) => $query->where('created_at', '>=', $date))
+            ->when(data_get($filters, 'created_before'), fn (Builder $query, string $date) => $query->where('created_at', '<=', $date))
+            ->when(data_get($filters, 'query'), function (Builder $query, string $search): void {
+                $like = '%'.$search.'%';
+
+                $query->where(function (Builder $query) use ($like): void {
+                    $query
+                        ->where('order_number', 'like', $like)
+                        ->orWhere('email', 'like', $like)
+                        ->orWhereHas('customer', fn (Builder $query) => $query->where('name', 'like', $like));
+                });
+            });
     }
 }
*** End Patch


Success. Updated the following files:
M app/Services/OrderExportService.php

@@ -4,2 +4,10 @@

+use App\Enums\ExportStatus;
+use App\Models\DataExport;
+use App\Models\Order;
+use App\Models\Store;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Support\Facades\Storage;
+use Throwable;
+
 class OrderExportService
@@ -7,7 +15,123 @@
     /**
-     * Create a new class instance.
+     * @param  array<string, mixed>  $filters
+     */
+    public function create(Store $store, array $filters): DataExport
+    {
+        $export = DataExport::query()->create([
+            'store_id' => $store->getKey(),
+            'type' => 'orders',
+            'format' => 'csv',
+            'status' => ExportStatus::Queued,
+            'filters_json' => $filters,
+        ]);
+
+        try {
+            $export->forceFill(['status' => ExportStatus::Processing])->save();
+
+            [$csv, $rowCount] = $this->csv($store, $filters);
+            $storageKey = "exports/orders/{$export->getKey()}.csv";
+
+            Storage::disk('local')->put($storageKey, $csv);
+
+            $export->forceFill([
+                'status' => ExportStatus::Completed,
+                'row_count' => $rowCount,
+                'storage_key' => $storageKey,
+                'download_expires_at' => now()->addHour(),
+                'completed_at' => now(),
+            ])->save();
+        } catch (Throwable $exception) {
+            $export->forceFill([
+                'status' => ExportStatus::Failed,
+                'error_message' => $exception->getMessage(),
+                'failed_at' => now(),
+            ])->save();
+
+            throw $exception;
+        }
+
+        return $export->refresh();
+    }
+
+    /**
+     * @param  array<string, mixed>  $filters
+     * @return array{0: string, 1: int}
+     */
+    private function csv(Store $store, array $filters): array
+    {
+        $handle = fopen('php://temp', 'r+');
+
+        fputcsv($handle, [
+            'order_number',
+            'created_at',
+            'status',
+            'financial_status',
+            'fulfillment_status',
+            'customer_email',
+            'customer_name',
+            'subtotal_amount',
+            'discount_amount',
+            'shipping_amount',
+            'tax_amount',
+            'total_amount',
+            'currency',
+            'shipping_method',
+            'tracking_number',
+        ]);
+
+        $rowCount = 0;
+
+        $this->query($store, $filters)
+            ->with(['customer', 'fulfillments'])
+            ->orderBy('id')
+            ->each(function (Order $order) use ($handle, &$rowCount): void {
+                fputcsv($handle, [
+                    $order->order_number,
+                    $order->created_at?->toIso8601String(),
+                    $order->status?->value,
+                    $order->financial_status?->value,
+                    $order->fulfillment_status?->value,
+                    $order->email,
+                    $order->customer?->name,
+                    $order->subtotal_amount,
+                    $order->discount_amount,
+                    $order->shipping_amount,
+                    $order->tax_amount,
+                    $order->total_amount,
+                    $order->currency,
+                    '',
+                    $order->fulfillments->first()?->tracking_number,
+                ]);
+
+                $rowCount++;
+            });
+
+        rewind($handle);
+
+        return [(string) stream_get_contents($handle), $rowCount];
+    }
+
+    /**
+     * @param  array<string, mixed>  $filters
+     * @return Builder<Order>
      */
-    public function __construct()
+    private function query(Store $store, array $filters): Builder
     {
-        //
+        return Order::withoutGlobalScopes()
+            ->where('store_id', $store->getKey())
+            ->when(data_get($filters, 'status'), fn (Builder $query, string $status) => $query->where('status', $status))
+            ->when(data_get($filters, 'financial_status'), fn (Builder $query, string $status) => $query->where('financial_status', $status))
+            ->when(data_get($filters, 'fulfillment_status'), fn (Builder $query, string $status) => $query->where('fulfillment_status', $status))
+            ->when(data_get($filters, 'created_after'), fn (Builder $query, string $date) => $query->where('created_at', '>=', $date))
+            ->when(data_get($filters, 'created_before'), fn (Builder $query, string $date) => $query->where('created_at', '<=', $date))
+            ->when(data_get($filters, 'query'), function (Builder $query, string $search): void {
+                $like = '%'.$search.'%';
+
+                $query->where(function (Builder $query) use ($like): void {
+                    $query
+                        ->where('order_number', 'like', $like)
+                        ->orWhere('email', 'like', $like)
+                        ->orWhereHas('customer', fn (Builder $query) => $query->where('name', 'like', $like));
+                });
+            });
     }


Output: {"output":"Success. Updated the following files:\nM app/Services/OrderExportService.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Update File: app/Services/OrderExportService.php
@@
 use Illuminate\Database\Eloquent\Builder;
 use Illuminate\Support\Facades\Storage;
+use RuntimeException;
 use Throwable;
@@
         $handle = fopen('php://temp', 'r+');

+        if ($handle === false) {
+            throw new RuntimeException('Unable to open temporary export stream.');
+        }
+
*** End Patch


@@ -10,2 +10,3 @@
 use Illuminate\Support\Facades\Storage;
+use RuntimeException;
 use Throwable;
@@ -63,2 +64,6 @@

+        if ($handle === false) {
+            throw new RuntimeException('Unable to open temporary export stream.');
+        }
+
         fputcsv($handle, [


Output: {"output":"Success. Updated the following files:\nM app/Services/OrderExportService.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Update File: app/Http/Controllers/Api/Admin/V1/OrderExportController.php
@@
 namespace App\Http\Controllers\Api\Admin\V1;

 use App\Http\Controllers\Controller;
+use App\Http\Resources\Admin\V1\DataExportResource;
+use App\Models\DataExport;
+use App\Models\Store;
+use App\Services\OrderExportService;
+use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
+use Illuminate\Validation\Rule;

 class OrderExportController extends Controller
 {
-    //
+    public function store(Request $request, Store $store, OrderExportService $exports): JsonResponse
+    {
+        $this->authorizeStore($request, $store);
+
+        $validated = $request->validate([
+            'format' => ['nullable', Rule::in(['csv'])],
+            'filters' => ['nullable', 'array'],
+            'filters.status' => ['nullable', Rule::in(['pending', 'paid', 'fulfilled', 'cancelled', 'refunded'])],
+            'filters.financial_status' => ['nullable', Rule::in(['pending', 'authorized', 'paid', 'partially_refunded', 'refunded', 'voided'])],
+            'filters.fulfillment_status' => ['nullable', Rule::in(['unfulfilled', 'partial', 'fulfilled'])],
+            'filters.query' => ['nullable', 'string', 'max:255'],
+            'filters.created_after' => ['nullable', 'date'],
+            'filters.created_before' => ['nullable', 'date', 'after_or_equal:filters.created_after'],
+        ]);
+
+        $export = $exports->create($store, $validated['filters'] ?? []);
+
+        return response()->json([
+            'export_id' => $export->getKey(),
+            'status' => $export->status?->value,
+            'created_at' => $export->created_at?->toIso8601String(),
+        ], 202);
+    }
+
+    public function show(Request $request, Store $store, DataExport $dataExport): DataExportResource
+    {
+        $this->authorizeStore($request, $store);
+        $this->abortUnlessExportBelongsToStore($dataExport, $store);
+
+        return DataExportResource::make($dataExport);
+    }
+
+    private function authorizeStore(Request $request, Store $store): void
+    {
+        if (! $request->attributes->has('admin_api_oauth_token')) {
+            abort_unless($request->user()?->stores()->whereKey($store->getKey())->exists(), 403);
+        }
+
+        app()->instance('current_store', $store);
+    }
+
+    private function abortUnlessExportBelongsToStore(DataExport $export, Store $store): void
+    {
+        abort_unless((int) $export->store_id === $store->getKey(), 404);
+    }
 }
*** End Patch


Success. Updated the following files:
M app/Http/Controllers/Api/Admin/V1/OrderExportController.php

@@ -5,3 +5,9 @@
 use App\Http\Controllers\Controller;
+use App\Http\Resources\Admin\V1\DataExportResource;
+use App\Models\DataExport;
+use App\Models\Store;
+use App\Services\OrderExportService;
+use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
+use Illuminate\Validation\Rule;

@@ -9,3 +15,47 @@
 {
-    //
+    public function store(Request $request, Store $store, OrderExportService $exports): JsonResponse
+    {
+        $this->authorizeStore($request, $store);
+
+        $validated = $request->validate([
+            'format' => ['nullable', Rule::in(['csv'])],
+            'filters' => ['nullable', 'array'],
+            'filters.status' => ['nullable', Rule::in(['pending', 'paid', 'fulfilled', 'cancelled', 'refunded'])],
+            'filters.financial_status' => ['nullable', Rule::in(['pending', 'authorized', 'paid', 'partially_refunded', 'refunded', 'voided'])],
+            'filters.fulfillment_status' => ['nullable', Rule::in(['unfulfilled', 'partial', 'fulfilled'])],
+            'filters.query' => ['nullable', 'string', 'max:255'],
+            'filters.created_after' => ['nullable', 'date'],
+            'filters.created_before' => ['nullable', 'date', 'after_or_equal:filters.created_after'],
+        ]);
+
+        $export = $exports->create($store, $validated['filters'] ?? []);
+
+        return response()->json([
+            'export_id' => $export->getKey(),
+            'status' => $export->status?->value,
+            'created_at' => $export->created_at?->toIso8601String(),
+        ], 202);
+    }
+
+    public function show(Request $request, Store $store, DataExport $dataExport): DataExportResource
+    {
+        $this->authorizeStore($request, $store);
+        $this->abortUnlessExportBelongsToStore($dataExport, $store);
+
+        return DataExportResource::make($dataExport);
+    }
+
+    private function authorizeStore(Request $request, Store $store): void
+    {
+        if (! $request->attributes->has('admin_api_oauth_token')) {
+            abort_unless($request->user()?->stores()->whereKey($store->getKey())->exists(), 403);
+        }
+
+        app()->instance('current_store', $store);
+    }
+
+    private function abortUnlessExportBelongsToStore(DataExport $export, Store $store): void
+    {
+        abort_unless((int) $export->store_id === $store->getKey(), 404);
+    }
 }


Output: {"output":"Success. Updated the following files:\nM app/Http/Controllers/Api/Admin/V1/OrderExportController.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Update File: app/Http/Resources/Admin/V1/DataExportResource.php
@@
 namespace App\Http\Resources\Admin\V1;

+use App\Enums\ExportStatus;
 use Illuminate\Http\Request;
 use Illuminate\Http\Resources\Json\JsonResource;
+use Illuminate\Support\Facades\Storage;

 class DataExportResource extends JsonResource
@@
     public function toArray(Request $request): array
     {
-        return parent::toArray($request);
+        return [
+            'id' => $this->id,
+            'store_id' => $this->store_id,
+            'type' => $this->type,
+            'status' => $this->status?->value,
+            'format' => $this->format,
+            'filters' => $this->filters_json ?? [],
+            'row_count' => $this->row_count,
+            'download_url' => $this->downloadUrl(),
+            'download_expires_at' => $this->download_expires_at?->toIso8601String(),
+            'error_message' => $this->error_message,
+            'created_at' => $this->created_at?->toIso8601String(),
+            'completed_at' => $this->completed_at?->toIso8601String(),
+            'failed_at' => $this->failed_at?->toIso8601String(),
+        ];
+    }
+
+    private function downloadUrl(): ?string
+    {
+        if ($this->status !== ExportStatus::Completed || ! is_string($this->storage_key)) {
+            return null;
+        }
+
+        if (! Storage::disk('local')->exists($this->storage_key)) {
+            return null;
+        }
+
+        return 'data:text/csv;charset=utf-8,'.rawurlencode(Storage::disk('local')->get($this->storage_key));
     }
 }
*** End Patch


Success. Updated the following files:
M app/Http/Resources/Admin/V1/DataExportResource.php

@@ -4,4 +4,6 @@

+use App\Enums\ExportStatus;
 use Illuminate\Http\Request;
 use Illuminate\Http\Resources\Json\JsonResource;
+use Illuminate\Support\Facades\Storage;

@@ -16,3 +18,30 @@
     {
-        return parent::toArray($request);
+        return [
+            'id' => $this->id,
+            'store_id' => $this->store_id,
+            'type' => $this->type,
+            'status' => $this->status?->value,
+            'format' => $this->format,
+            'filters' => $this->filters_json ?? [],
+            'row_count' => $this->row_count,
+            'download_url' => $this->downloadUrl(),
+            'download_expires_at' => $this->download_expires_at?->toIso8601String(),
+            'error_message' => $this->error_message,
+            'created_at' => $this->created_at?->toIso8601String(),
+            'completed_at' => $this->completed_at?->toIso8601String(),
+            'failed_at' => $this->failed_at?->toIso8601String(),
+        ];
+    }
+
+    private function downloadUrl(): ?string
+    {
+        if ($this->status !== ExportStatus::Completed || ! is_string($this->storage_key)) {
+            return null;
+        }
+
+        if (! Storage::disk('local')->exists($this->storage_key)) {
+            return null;
+        }
+
+        return 'data:text/csv;charset=utf-8,'.rawurlencode(Storage::disk('local')->get($this->storage_key));
     }


Output: {"output":"Success. Updated the following files:\nM app/Http/Resources/Admin/V1/DataExportResource.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


Input:
*** Begin Patch
*** Update File: routes/api.php
@@
 use App\Http\Controllers\Api\Admin\V1\OrderController as AdminOrderController;
+use App\Http\Controllers\Api\Admin\V1\OrderExportController as AdminOrderExportController;
 use App\Http\Controllers\Api\Admin\V1\OrderFulfillmentController as AdminOrderFulfillmentController;
@@
         Route::middleware('admin.api:read-orders')->group(function (): void {
+            Route::post('exports/orders', [AdminOrderExportController::class, 'store'])->name('exports.orders.store');
+            Route::get('exports/{dataExport}', [AdminOrderExportController::class, 'show'])->name('exports.show');
             Route::get('orders', [AdminOrderController::class, 'index'])->name('orders.index');
             Route::get('orders/{order}', [AdminOrderController::class, 'show'])->name('orders.show');
         });
*** End Patch


Success. Updated the following files:
M routes/api.php

@@ -7,2 +7,3 @@
 use App\Http\Controllers\Api\Admin\V1\OrderController as AdminOrderController;
+use App\Http\Controllers\Api\Admin\V1\OrderExportController as AdminOrderExportController;
 use App\Http\Controllers\Api\Admin\V1\OrderFulfillmentController as AdminOrderFulfillmentController;
@@ -132,2 +133,4 @@
         Route::middleware('admin.api:read-orders')->group(function (): void {
+            Route::post('exports/orders', [AdminOrderExportController::class, 'store'])->name('exports.orders.store');
+            Route::get('exports/{dataExport}', [AdminOrderExportController::class, 'show'])->name('exports.show');
             Route::get('orders', [AdminOrderController::class, 'index'])->name('orders.index');


Output: {"output":"Success. Updated the following files:\nM routes/api.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


sed -n '1,180p' database/factories/FulfillmentFactory.php


sed -n '1,180p' database/factories/FulfillmentFactory.php

<?php

namespace Database\Factories;

use App\Enums\FulfillmentShipmentStatus;
use App\Models\Order;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Fulfillment>
 */
class FulfillmentFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'order_id' => Order::factory(),
            'status' => FulfillmentShipmentStatus::Pending,
            'tracking_company' => null,
            'tracking_number' => null,
            'tracking_url' => null,
            'shipped_at' => null,
            'delivered_at' => null,
        ];
    }

    public function withTracking(): static
    {
        return $this->state(fn (array $attributes): array => [
            'tracking_company' => fake()->randomElement(['DHL', 'UPS', 'DPD']),
            'tracking_number' => fake()->bothify('??##########'),
            'tracking_url' => fake()->url(),
        ]);
    }

    public function shipped(): static
    {
        return $this->withTracking()->state(fn (array $attributes): array => [
            'status' => FulfillmentShipmentStatus::Shipped,
            'shipped_at' => now(),
        ]);
    }

    public function delivered(): static
    {
        return $this->withTracking()->state(fn (array $attributes): array => [
            'status' => FulfillmentShipmentStatus::Delivered,
            'shipped_at' => now()->subDay(),
            'delivered_at' => now(),
        ]);
    }
}