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

<?php

use App\Models\Store;
use App\Models\StoreSettings;
use App\Models\User;
use App\Services\WebhookService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;

uses(RefreshDatabase::class);

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

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

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

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

test('platform api creates organizations and stores for owner sessions', function (): void {
    $user = adminPlatformApiUser();

    $organizationResponse = $this->actingAs($user)
        ->postJson('/api/admin/v1/platform/organizations', [
            'name' => 'Platform API Org',
            'billing_email' => 'billing@platform-api.test',
        ])
        ->assertCreated()
        ->assertJsonPath('data.name', 'Platform API Org')
        ->assertJsonPath('data.billing_email', 'billing@platform-api.test');

    $organizationId = $organizationResponse->json('data.id');

    $this->actingAs($user)
        ->postJson('/api/admin/v1/platform/stores', [
            'organization_id' => $organizationId,
            'name' => 'Platform API Store',
            'handle' => 'platform-api-store',
            'default_currency' => 'EUR',
            'default_locale' => 'en',
            'timezone' => 'Europe/Berlin',
        ])
        ->assertCreated()
        ->assertJsonPath('data.name', 'Platform API Store')
        ->assertJsonPath('data.handle', 'platform-api-store')
        ->assertJsonPath('data.status', 'active');

    $store = Store::query()->where('handle', 'platform-api-store')->firstOrFail();

    expect(StoreSettings::query()->whereKey($store->getKey())->exists())->toBeTrue()
        ->and(DB::table('store_users')
            ->where('store_id', $store->getKey())
            ->where('user_id', $user->getKey())
            ->where('role', 'owner')
            ->exists())->toBeTrue();
});

test('platform api rejects non owner sessions and tokens without manage platform ability', function (): void {
    $store = adminPlatformApiStore();
    $staff = User::factory()->create([
        'email' => 'platform-staff@example.test',
    ]);
    DB::table('store_users')->insert([
        'store_id' => $store->getKey(),
        'user_id' => $staff->getKey(),
        'role' => 'staff',
        'created_at' => now(),
    ]);
    $readToken = adminPlatformApiToken($store, ['read-products']);

    $this->actingAs($staff)
        ->postJson('/api/admin/v1/platform/organizations', [
            'name' => 'Forbidden Org',
            'billing_email' => 'forbidden@example.test',
        ])
        ->assertForbidden();

    $this->withToken($readToken['plain_text'])
        ->postJson('/api/admin/v1/platform/organizations', [
            'name' => 'Forbidden Token Org',
            'billing_email' => 'forbidden-token@example.test',
        ])
        ->assertForbidden();
});

test('platform api accepts manage platform bearer tokens', function (): void {
    $store = adminPlatformApiStore();
    $token = adminPlatformApiToken($store, ['manage-platform']);

    $this->withToken($token['plain_text'])
        ->postJson('/api/admin/v1/platform/organizations', [
            'name' => 'Token Platform Org',
            'billing_email' => 'token-platform@example.test',
        ])
        ->assertCreated()
        ->assertJsonPath('data.name', 'Token Platform Org');

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

test('store me and invite endpoints expose membership and enforce role or ability', function (): void {
    $store = adminPlatformApiStore();
    $user = adminPlatformApiUser();
    $staff = User::factory()->create([
        'email' => 'store-staff@example.test',
    ]);
    DB::table('store_users')->insert([
        'store_id' => $store->getKey(),
        'user_id' => $staff->getKey(),
        'role' => 'staff',
        'created_at' => now(),
    ]);
    $readToken = adminPlatformApiToken($store, ['read-products']);
    $manageToken = adminPlatformApiToken($store, ['manage-platform']);

    $this->withToken($readToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/invites", [
            'email' => 'new-staff@example.test',
            'role' => 'staff',
        ])
        ->assertForbidden();

    $this->withToken($manageToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/invites", [
            'email' => 'new-staff@example.test',
            'role' => 'staff',
        ])
        ->assertCreated()
        ->assertJsonPath('data.email', 'new-staff@example.test')
        ->assertJsonPath('data.role', 'staff');

    $this->actingAs($user)
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/me")
        ->assertOk()
        ->assertJsonPath('data.email', 'admin@acme.test')
        ->assertJsonPath('data.role', 'owner')
        ->assertJsonPath('data.permissions.1', 'read-products');

    $this->actingAs($staff)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/invites", [
            'email' => 'new-staff@example.test',
            'role' => 'staff',
        ])
        ->assertForbidden();

    $this->actingAs($user)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/invites", [
            'email' => 'admin@acme.test',
            'role' => 'admin',
        ])
        ->assertStatus(409);
});


Output:
Chunk ID: 4520fe
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 406
Output:
<?php

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

use App\Http\Controllers\Controller;
use App\Http\Requests\Api\Admin\V1\StorePlatformStoreRequest;
use App\Http\Resources\Admin\V1\StoreResource;
use App\Models\Store;
use App\Models\StoreSettings;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;

class PlatformStoreController extends Controller
{
    public function store(StorePlatformStoreRequest $request): JsonResponse
    {
        $validated = $request->validated();

        $store = DB::transaction(function () use ($request, $validated): Store {
            $store = Store::query()->create([
                'organization_id' => $validated['organization_id'],
                'name' => $validated['name'],
                'handle' => $validated['handle'],
                'status' => 'active',
                'default_currency' => strtoupper((string) $validated['default_currency']),
                'default_locale' => $validated['default_locale'],
                'timezone' => $validated['timezone'],
            ]);

            StoreSettings::query()->create([
                'store_id' => $store->getKey(),
                'settings_json' => [],
            ]);

            $user = $request->user();

            if ($user instanceof \App\Models\User) {
                $store->users()->syncWithoutDetaching([
                    $user->getKey() => ['role' => 'owner', 'created_at' => now()],
                ]);
            }

            return $store;
        });

        return StoreResource::make($store)
            ->response()
            ->setStatusCode(201);
    }
}


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

namespace App\Http\Middleware;

use App\Enums\AppInstallationStatus;
use App\Enums\StoreUserRole;
use App\Models\OauthToken;
use App\Models\User;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class AuthenticatePlatformApi
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        app()->forgetInstance('admin_api_oauth_token');

        $user = $request->user();

        if ($user instanceof User) {
            if ($this->userCanManagePlatform($user)) {
                return $next($request);
            }

            return response()->json(['message' => 'Forbidden.'], 403);
        }

        $plainTextToken = $request->bearerToken();

        if (! is_string($plainTextToken) || $plainTextToken === '') {
            return response()->json(['message' => 'Unauthenticated.'], 401);
        }

        $token = OauthToken::query()
            ->with('installation')
            ->where('access_token_hash', hash('sha256', $plainTextToken))
            ->first();

        if (! $token instanceof OauthToken || $token->isExpired()) {
            return response()->json(['message' => 'Unauthenticated.'], 401);
        }

        $installation = $token->installation;

        if ($installation === null || $installation->status !== AppInstallationStatus::Active) {
            return response()->json(['message' => 'Forbidden.'], 403);
        }

        if (! $this->hasManagePlatformAbility($token)) {
            return response()->json(['message' => 'Forbidden.'], 403);
        }

        $token->forceFill(['last_used_at' => now()])->save();

        $request->attributes->set('admin_api_oauth_token', $token);
        app()->instance('admin_api_oauth_token', $token);

        return $next($request);
    }

    private function userCanManagePlatform(User $user): bool
    {
        return $user->stores()
            ->wherePivot('role', StoreUserRole::Owner->value)
            ->exists();
    }

    private function hasManagePlatformAbility(OauthToken $token): bool
    {
        $abilities = $token->abilities_json ?? [];

        return in_array('*', $abilities, true) || in_array('manage-platform', $abilities, true);
    }
}


Output:
Chunk ID: 404bf2
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1469
Output:
<?php

use App\Models\Store;
use App\Models\StoreSettings;
use App\Models\User;
use App\Services\WebhookService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;

uses(RefreshDatabase::class);

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

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

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

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

test('platform api creates organizations and stores for owner sessions', function (): void {
    $user = adminPlatformApiUser();

    $organizationResponse = $this->actingAs($user)
        ->postJson('/api/admin/v1/platform/organizations', [
            'name' => 'Platform API Org',
            'billing_email' => 'billing@platform-api.test',
        ])
        ->assertCreated()
        ->assertJsonPath('data.name', 'Platform API Org')
        ->assertJsonPath('data.billing_email', 'billing@platform-api.test');

    $organizationId = $organizationResponse->json('data.id');

    $this->actingAs($user)
        ->postJson('/api/admin/v1/platform/stores', [
            'organization_id' => $organizationId,
            'name' => 'Platform API Store',
            'handle' => 'platform-api-store',
            'default_currency' => 'EUR',
            'default_locale' => 'en',
            'timezone' => 'Europe/Berlin',
        ])
        ->assertCreated()
        ->assertJsonPath('data.name', 'Platform API Store')
        ->assertJsonPath('data.handle', 'platform-api-store')
        ->assertJsonPath('data.status', 'active');

    $store = Store::query()->where('handle', 'platform-api-store')->firstOrFail();

    expect(StoreSettings::query()->whereKey($store->getKey())->exists())->toBeTrue()
        ->and(DB::table('store_users')
            ->where('store_id', $store->getKey())
            ->where('user_id', $user->getKey())
            ->where('role', 'owner')
            ->exists())->toBeTrue();
});

test('platform api rejects non owner sessions and tokens without manage platform ability', function (): void {
    $store = adminPlatformApiStore();
    $staff = User::factory()->create([
        'email' => 'platform-staff@example.test',
    ]);
    DB::table('store_users')->insert([
        'store_id' => $store->getKey(),
        'user_id' => $staff->getKey(),
        'role' => 'staff',
        'created_at' => now(),
    ]);
    $readToken = adminPlatformApiToken($store, ['read-products']);

    $this->actingAs($staff)
        ->postJson('/api/admin/v1/platform/organizations', [
            'name' => 'Forbidden Org',
            'billing_email' => 'forbidden@example.test',
        ])
        ->assertForbidden();

    $this->withToken($readToken['plain_text'])
        ->postJson('/api/admin/v1/platform/organizations', [
            'name' => 'Forbidden Token Org',
            'billing_email' => 'forbidden-token@example.test',
        ])
        ->assertForbidden();
});

test('platform api accepts manage platform bearer tokens', function (): void {
    $store = adminPlatformApiStore();
    $token = adminPlatformApiToken($store, ['manage-platform']);

    $this->withToken($token['plain_text'])
        ->postJson('/api/admin/v1/platform/organizations', [
            'name' => 'Token Platform Org',
            'billing_email' => 'token-platform@example.test',
        ])
        ->assertCreated()
        ->assertJsonPath('data.name', 'Token Platform Org');

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

test('store me and invite endpoints expose membership and enforce role or ability', function (): void {
    $store = adminPlatformApiStore();
    $user = adminPlatformApiUser();
    $staff = User::factory()->create([
        'email' => 'store-staff@example.test',
    ]);
    DB::table('store_users')->insert([
        'store_id' => $store->getKey(),
        'user_id' => $staff->getKey(),
        'role' => 'staff',
        'created_at' => now(),
    ]);
    $readToken = adminPlatformApiToken($store, ['read-products']);
    $manageToken = adminPlatformApiToken($store, ['manage-platform']);

    $this->withToken($readToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/invites", [
            'email' => 'new-staff@example.test',
            'role' => 'staff',
        ])
        ->assertForbidden();

    $this->withToken($manageToken['plain_text'])
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/invites", [
            'email' => 'new-staff@example.test',
            'role' => 'staff',
        ])
        ->assertCreated()
        ->assertJsonPath('data.email', 'new-staff@example.test')
        ->assertJsonPath('data.role', 'staff');

    $this->actingAs($user)
        ->getJson("/api/admin/v1/stores/{$store->getKey()}/me")
        ->assertOk()
        ->assertJsonPath('data.email', 'admin@acme.test')
        ->assertJsonPath('data.role', 'owner')
        ->assertJsonPath('data.permissions.1', 'read-products');

    $this->actingAs($staff)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/invites", [
            'email' => 'new-staff@example.test',
            'role' => 'staff',
        ])
        ->assertForbidden();

    $this->actingAs($user)
        ->postJson("/api/admin/v1/stores/{$store->getKey()}/invites", [
            'email' => 'admin@acme.test',
            'role' => 'admin',
        ])
        ->assertStatus(409);
});


Output:
Chunk ID: d121e9
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 0
Output:


 .../Admin/V1/PlatformOrganizationController.php    |  21 +++
 .../Api/Admin/V1/PlatformStoreController.php       |  50 ++++++
 .../Api/Admin/V1/StoreInviteController.php         |  50 ++++++
 .../Api/Admin/V1/StoreMembershipController.php     |  95 ++++++++++++
 app/Http/Middleware/AuthenticatePlatformApi.php    |  80 ++++++++++
 .../Requests/Api/Admin/V1/StoreInviteRequest.php   |  25 +++
 .../Admin/V1/StorePlatformOrganizationRequest.php  |  24 +++
 .../Api/Admin/V1/StorePlatformStoreRequest.php     |  35 +++++
 .../Resources/Admin/V1/OrganizationResource.php    |  25 +++
 app/Http/Resources/Admin/V1/StoreResource.php      |  29 ++++
 bootstrap/app.php                                  |   2 +
 routes/api.php                                     |  21 +++
 specs/progress.md                                  |  13 +-
 tests/Feature/Api/AdminPlatformApiTest.php         | 169 +++++++++++++++++++++
 14 files changed, 635 insertions(+), 4 deletions(-)


A  app/Http/Controllers/Api/Admin/V1/PlatformOrganizationController.php
A  app/Http/Controllers/Api/Admin/V1/PlatformStoreController.php
A  app/Http/Controllers/Api/Admin/V1/StoreInviteController.php
A  app/Http/Controllers/Api/Admin/V1/StoreMembershipController.php
A  app/Http/Middleware/AuthenticatePlatformApi.php
A  app/Http/Requests/Api/Admin/V1/StoreInviteRequest.php
A  app/Http/Requests/Api/Admin/V1/StorePlatformOrganizationRequest.php
A  app/Http/Requests/Api/Admin/V1/StorePlatformStoreRequest.php
A  app/Http/Resources/Admin/V1/OrganizationResource.php
A  app/Http/Resources/Admin/V1/StoreResource.php
M  bootstrap/app.php
M  routes/api.php
M  specs/progress.md
A  tests/Feature/Api/AdminPlatformApiTest.php
?? .playwright-mcp/console-2026-05-03T23-28-42-443Z.log
?? .playwright-mcp/console-2026-05-04T04-24-15-455Z.log
?? .playwright-mcp/console-2026-05-04T13-33-19-511Z.log


Output:
Chunk ID: e39296
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 241
Output:
 .../Admin/V1/PlatformOrganizationController.php    |  21 +++
 .../Api/Admin/V1/PlatformStoreController.php       |  50 ++++++
 .../Api/Admin/V1/StoreInviteController.php         |  50 ++++++
 .../Api/Admin/V1/StoreMembershipController.php     |  95 ++++++++++++
 app/Http/Middleware/AuthenticatePlatformApi.php    |  80 ++++++++++
 .../Requests/Api/Admin/V1/StoreInviteRequest.php   |  25 +++
 .../Admin/V1/StorePlatformOrganizationRequest.php  |  24 +++
 .../Api/Admin/V1/StorePlatformStoreRequest.php     |  35 +++++
 .../Resources/Admin/V1/OrganizationResource.php    |  25 +++
 app/Http/Resources/Admin/V1/StoreResource.php      |  29 ++++
 bootstrap/app.php                                  |   2 +
 routes/api.php                                     |  21 +++
 specs/progress.md                                  |  13 +-
 tests/Feature/Api/AdminPlatformApiTest.php         | 169 +++++++++++++++++++++
 14 files changed, 635 insertions(+), 4 deletions(-)


Output:
Chunk ID: 9b3a46
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 223
Output:
A  app/Http/Controllers/Api/Admin/V1/PlatformOrganizationController.php
A  app/Http/Controllers/Api/Admin/V1/PlatformStoreController.php
A  app/Http/Controllers/Api/Admin/V1/StoreInviteController.php
A  app/Http/Controllers/Api/Admin/V1/StoreMembershipController.php
A  app/Http/Middleware/AuthenticatePlatformApi.php
A  app/Http/Requests/Api/Admin/V1/StoreInviteRequest.php
A  app/Http/Requests/Api/Admin/V1/StorePlatformOrganizationRequest.php
A  app/Http/Requests/Api/Admin/V1/StorePlatformStoreRequest.php
A  app/Http/Resources/Admin/V1/OrganizationResource.php
A  app/Http/Resources/Admin/V1/StoreResource.php
M  bootstrap/app.php
M  routes/api.php
M  specs/progress.md
A  tests/Feature/Api/AdminPlatformApiTest.php
?? .playwright-mcp/console-2026-05-03T23-28-42-443Z.log
?? .playwright-mcp/console-2026-05-04T04-24-15-455Z.log
?? .playwright-mcp/console-2026-05-04T13-33-19-511Z.log


git commit -m "Add admin platform API endpoints"


git commit -m "Add admin platform API endpoints"

[2026-05-04-codex-5-5-goal ccbc796f] Add admin platform API endpoints
 14 files changed, 635 insertions(+), 4 deletions(-)
 create mode 100644 app/Http/Controllers/Api/Admin/V1/PlatformOrganizationController.php
 create mode 100644 app/Http/Controllers/Api/Admin/V1/PlatformStoreController.php
 create mode 100644 app/Http/Controllers/Api/Admin/V1/StoreInviteController.php
 create mode 100644 app/Http/Controllers/Api/Admin/V1/StoreMembershipController.php
 create mode 100644 app/Http/Middleware/AuthenticatePlatformApi.php
 create mode 100644 app/Http/Requests/Api/Admin/V1/StoreInviteRequest.php
 create mode 100644 app/Http/Requests/Api/Admin/V1/StorePlatformOrganizationRequest.php
 create mode 100644 app/Http/Requests/Api/Admin/V1/StorePlatformStoreRequest.php
 create mode 100644 app/Http/Resources/Admin/V1/OrganizationResource.php
 create mode 100644 app/Http/Resources/Admin/V1/StoreResource.php
 create mode 100644 tests/Feature/Api/AdminPlatformApiTest.php


Output:
Chunk ID: ee2ce4
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 243
Output:
[2026-05-04-codex-5-5-goal ccbc796f] Add admin platform API endpoints
 14 files changed, 635 insertions(+), 4 deletions(-)
 create mode 100644 app/Http/Controllers/Api/Admin/V1/PlatformOrganizationController.php
 create mode 100644 app/Http/Controllers/Api/Admin/V1/PlatformStoreController.php
 create mode 100644 app/Http/Controllers/Api/Admin/V1/StoreInviteController.php
 create mode 100644 app/Http/Controllers/Api/Admin/V1/StoreMembershipController.php
 create mode 100644 app/Http/Middleware/AuthenticatePlatformApi.php
 create mode 100644 app/Http/Requests/Api/Admin/V1/StoreInviteRequest.php
 create mode 100644 app/Http/Requests/Api/Admin/V1/StorePlatformOrganizationRequest.php
 create mode 100644 app/Http/Requests/Api/Admin/V1/StorePlatformStoreRequest.php
 create mode 100644 app/Http/Resources/Admin/V1/OrganizationResource.php
 create mode 100644 app/Http/Resources/Admin/V1/StoreResource.php
 create mode 100644 tests/Feature/Api/AdminPlatformApiTest.php


Committed the platform route fix as `ccbc796f Add admin platform API endpoints`. I’m continuing the completion audit against the remaining partial checklist areas.


Output:
Chunk ID: 4777ee
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 42
Output:
?? .playwright-mcp/console-2026-05-03T23-28-42-443Z.log
?? .playwright-mcp/console-2026-05-04T04-24-15-455Z.log
?? .playwright-mcp/console-2026-05-04T13-33-19-511Z.log


php artisan route:list --path=api --except-vendor


Output:
Chunk ID: 5bc550
Wall time: 0.2243 seconds
Process exited with code 0
Original token count: 1345
Output:

  POST      api/admin/v1/platform/organizations api.admin.v1.platform.organiz…
  POST      api/admin/v1/platform/stores api.admin.v1.platform.stores.store  …
  GET|HEAD  api/admin/v1/stores/{store}/analytics/summary api.admin.v1.analyt…
  GET|HEAD  api/admin/v1/stores/{store}/collections api.admin.v1.collections.…
  POST      api/admin/v1/stores/{store}/collections api.admin.v1.collections.…
  PUT       api/admin/v1/stores/{store}/collections/{collection} api.admin.v1…
  DELETE    api/admin/v1/stores/{store}/collections/{collection} api.admin.v1…
  GET|HEAD  api/admin/v1/stores/{store}/customers api.admin.v1.customers.inde…
  GET|HEAD  api/admin/v1/stores/{store}/customers/{customer} api.admin.v1.cus…
  GET|HEAD  api/admin/v1/stores/{store}/discounts api.admin.v1.discounts.inde…
  POST      api/admin/v1/stores/{store}/discounts api.admin.v1.discounts.stor…
  PUT       api/admin/v1/stores/{store}/discounts/{discount} api.admin.v1.dis…
  DELETE    api/admin/v1/stores/{store}/discounts/{discount} api.admin.v1.dis…
  POST      api/admin/v1/stores/{store}/exports/orders api.admin.v1.exports.o…
  GET|HEAD  api/admin/v1/stores/{store}/exports/{dataExport} api.admin.v1.exp…
  POST      api/admin/v1/stores/{store}/invites api.admin.v1.stores.invites.s…
  GET|HEAD  api/admin/v1/stores/{store}/me api.admin.v1.stores.me › Api\Admin…
  GET|HEAD  api/admin/v1/stores/{store}/orders api.admin.v1.orders.index › Ap…
  GET|HEAD  api/admin/v1/stores/{store}/orders/{order} api.admin.v1.orders.sh…
  POST      api/admin/v1/stores/{store}/orders/{order}/fulfillments api.admin…
  POST      api/admin/v1/stores/{store}/orders/{order}/refunds api.admin.v1.o…
  GET|HEAD  api/admin/v1/stores/{store}/pages api.admin.v1.pages.index › Api\…
  POST      api/admin/v1/stores/{store}/pages api.admin.v1.pages.store › Api\…
  PUT       api/admin/v1/stores/{store}/pages/{page} api.admin.v1.pages.updat…
  DELETE    api/admin/v1/stores/{store}/pages/{page} api.admin.v1.pages.destr…
  GET|HEAD  api/admin/v1/stores/{store}/products api.admin.v1.products.index …
  POST      api/admin/v1/stores/{store}/products api.admin.v1.products.store …
  GET|HEAD  api/admin/v1/stores/{store}/products/{product} api.admin.v1.produ…
  PUT       api/admin/v1/stores/{store}/products/{product} api.admin.v1.produ…
  DELETE    api/admin/v1/stores/{store}/products/{product} api.admin.v1.produ…
  POST      api/admin/v1/stores/{store}/products/{product}/media/presign-upload api.admin.v1.products.media.presign-upload › Api\Admin\V1\ProductController@presignUpl…
  POST      api/admin/v1/stores/{store}/search/reindex api.admin.v1.search.re…
  GET|HEAD  api/admin/v1/stores/{store}/search/status api.admin.v1.search.sta…
  GET|HEAD  api/admin/v1/stores/{store}/settings api.admin.v1.settings.show  …
  PUT       api/admin/v1/stores/{store}/settings api.admin.v1.settings.update…
  GET|HEAD  api/admin/v1/stores/{store}/shipping/zones api.admin.v1.shipping.…
  POST      api/admin/v1/stores/{store}/shipping/zones api.admin.v1.shipping.…
  PUT       api/admin/v1/stores/{store}/shipping/zones/{shippingZone} api.adm…
  POST      api/admin/v1/stores/{store}/shipping/zones/{shippingZone}/rates a…
  GET|HEAD  api/admin/v1/stores/{store}/tax/settings api.admin.v1.tax.setting…
  PUT       api/admin/v1/stores/{store}/tax/settings api.admin.v1.tax.setting…
  POST      api/admin/v1/stores/{store}/themes api.admin.v1.themes.store › Ap…
  POST      api/admin/v1/stores/{store}/themes/{theme}/publish api.admin.v1.t…
  PUT       api/admin/v1/stores/{store}/themes/{theme}/settings api.admin.v1.…
  GET|HEAD  api/apps/v1/stores/{store}/customers api.apps.v1.customers.index …
  GET|HEAD  api/apps/v1/stores/{store}/orders api.apps.v1.orders.index › Api\…
  GET|HEAD  api/apps/v1/stores/{store}/products api.apps.v1.products.index › …
  POST      api/storefront/v1/analytics/events api.storefront.v1.analytics.ev…
  POST      api/storefront/v1/carts api.storefront.v1.carts.store › Api\Store…
  GET|HEAD  api/storefront/v1/carts/{cart} api.storefront.v1.carts.show › Api…
  POST      api/storefront/v1/carts/{cart}/lines api.storefront.v1.carts.line…
  PUT       api/storefront/v1/carts/{cart}/lines/{cartLine} api.storefront.v1…
  DELETE    api/storefront/v1/carts/{cart}/lines/{cartLine} api.storefront.v1…
  POST      api/storefront/v1/checkouts api.storefront.v1.checkouts.store › A…
  GET|HEAD  api/storefront/v1/checkouts/{checkout} api.storefront.v1.checkout…
  PUT       api/storefront/v1/checkouts/{checkout}/address api.storefront.v1.…
  POST      api/storefront/v1/checkouts/{checkout}/apply-discount api.storefr…
  DELETE    api/storefront/v1/checkouts/{checkout}/discount api.storefront.v1…
  POST      api/storefront/v1/checkouts/{checkout}/pay api.storefront.v1.chec…
  PUT       api/storefront/v1/checkouts/{checkout}/payment-method api.storefr…
  PUT       api/storefront/v1/checkouts/{checkout}/shipping-method api.storef…
  GET|HEAD  api/storefront/v1/orders/{orderNumber} api.storefront.v1.orders.s…
  GET|HEAD  api/storefront/v1/search api.storefront.v1.search.index › Api\Sto…
  GET|HEAD  api/storefront/v1/search/suggest api.storefront.v1.search.suggest…

                                                           Showing [64] routes


php artisan route:list --path=api --except-vendor

  POST      api/admin/v1/platform/organizations api.admin.v1.platform.organiz…
  POST      api/admin/v1/platform/stores api.admin.v1.platform.stores.store  …
  GET|HEAD  api/admin/v1/stores/{store}/analytics/summary api.admin.v1.analyt…
  GET|HEAD  api/admin/v1/stores/{store}/collections api.admin.v1.collections.…
  POST      api/admin/v1/stores/{store}/collections api.admin.v1.collections.…
  PUT       api/admin/v1/stores/{store}/collections/{collection} api.admin.v1…
  DELETE    api/admin/v1/stores/{store}/collections/{collection} api.admin.v1…
  GET|HEAD  api/admin/v1/stores/{store}/customers api.admin.v1.customers.inde…
  GET|HEAD  api/admin/v1/stores/{store}/customers/{customer} api.admin.v1.cus…
  GET|HEAD  api/admin/v1/stores/{store}/discounts api.admin.v1.discounts.inde…
  POST      api/admin/v1/stores/{store}/discounts api.admin.v1.discounts.stor…
  PUT       api/admin/v1/stores/{store}/discounts/{discount} api.admin.v1.dis…
  DELETE    api/admin/v1/stores/{store}/discounts/{discount} api.admin.v1.dis…
  POST      api/admin/v1/stores/{store}/exports/orders api.admin.v1.exports.o…
  GET|HEAD  api/admin/v1/stores/{store}/exports/{dataExport} api.admin.v1.exp…
  POST      api/admin/v1/stores/{store}/invites api.admin.v1.stores.invites.s…
  GET|HEAD  api/admin/v1/stores/{store}/me api.admin.v1.stores.me › Api\Admin…
  GET|HEAD  api/admin/v1/stores/{store}/orders api.admin.v1.orders.index › Ap…
  GET|HEAD  api/admin/v1/stores/{store}/orders/{order} api.admin.v1.orders.sh…
  POST      api/admin/v1/stores/{store}/orders/{order}/fulfillments api.admin…
  POST      api/admin/v1/stores/{store}/orders/{order}/refunds api.admin.v1.o…
  GET|HEAD  api/admin/v1/stores/{store}/pages api.admin.v1.pages.index › Api\…
  POST      api/admin/v1/stores/{store}/pages api.admin.v1.pages.store › Api\…
  PUT       api/admin/v1/stores/{store}/pages/{page} api.admin.v1.pages.updat…
  DELETE    api/admin/v1/stores/{store}/pages/{page} api.admin.v1.pages.destr…
  GET|HEAD  api/admin/v1/stores/{store}/products api.admin.v1.products.index …
  POST      api/admin/v1/stores/{store}/products api.admin.v1.products.store …
  GET|HEAD  api/admin/v1/stores/{store}/products/{product} api.admin.v1.produ…
  PUT       api/admin/v1/stores/{store}/products/{product} api.admin.v1.produ…
  DELETE    api/admin/v1/stores/{store}/products/{product} api.admin.v1.produ…
  POST      api/admin/v1/stores/{store}/products/{product}/media/presign-upload api.admin.v1.products.media.presign-upload › Api\Admin\V1\ProductController@presignUpl…
  POST      api/admin/v1/stores/{store}/search/reindex api.admin.v1.search.re…
  GET|HEAD  api/admin/v1/stores/{store}/search/status api.admin.v1.search.sta…
  GET|HEAD  api/admin/v1/stores/{store}/settings api.admin.v1.settings.show  …
  PUT       api/admin/v1/stores/{store}/settings api.admin.v1.settings.update…
  GET|HEAD  api/admin/v1/stores/{store}/shipping/zones api.admin.v1.shipping.…
  POST      api/admin/v1/stores/{store}/shipping/zones api.admin.v1.shipping.…
  PUT       api/admin/v1/stores/{store}/shipping/zones/{shippingZone} api.adm…
  POST      api/admin/v1/stores/{store}/shipping/zones/{shippingZone}/rates a…
  GET|HEAD  api/admin/v1/stores/{store}/tax/settings api.admin.v1.tax.setting…
  PUT       api/admin/v1/stores/{store}/tax/settings api.admin.v1.tax.setting…
  POST      api/admin/v1/stores/{store}/themes api.admin.v1.themes.store › Ap…
  POST      api/admin/v1/stores/{store}/themes/{theme}/publish api.admin.v1.t…
  PUT       api/admin/v1/stores/{store}/themes/{theme}/settings api.admin.v1.…
  GET|HEAD  api/apps/v1/stores/{store}/customers api.apps.v1.customers.index …
  GET|HEAD  api/apps/v1/stores/{store}/orders api.apps.v1.orders.index › Api\…
  GET|HEAD  api/apps/v1/stores/{store}/products api.apps.v1.products.index › …
  POST      api/storefront/v1/analytics/events api.storefront.v1.analytics.ev…
  POST      api/storefront/v1/carts api.storefront.v1.carts.store › Api\Store…
  GET|HEAD  api/storefront/v1/carts/{cart} api.storefront.v1.carts.show › Api…
  POST      api/storefront/v1/carts/{cart}/lines api.storefront.v1.carts.line…
  PUT       api/storefront/v1/carts/{cart}/lines/{cartLine} api.storefront.v1…
  DELETE    api/storefront/v1/carts/{cart}/lines/{cartLine} api.storefront.v1…
  POST      api/storefront/v1/checkouts api.storefront.v1.checkouts.store › A…
  GET|HEAD  api/storefront/v1/checkouts/{checkout} api.storefront.v1.checkout…
  PUT       api/storefront/v1/checkouts/{checkout}/address api.storefront.v1.…
  POST      api/storefront/v1/checkouts/{checkout}/apply-discount api.storefr…
  DELETE    api/storefront/v1/checkouts/{checkout}/discount api.storefront.v1…
  POST      api/storefront/v1/checkouts/{checkout}/pay api.storefront.v1.chec…
  PUT       api/storefront/v1/checkouts/{checkout}/payment-method api.storefr…
  PUT       api/storefront/v1/checkouts/{checkout}/shipping-method api.storef…
  GET|HEAD  api/storefront/v1/orders/{orderNumber} api.storefront.v1.orders.s…
  GET|HEAD  api/storefront/v1/search api.storefront.v1.search.index › Api\Sto…
  GET|HEAD  api/storefront/v1/search/suggest api.storefront.v1.search.suggest…

                                                           Showing [64] routes


rg -n "partial|deferred|TODO|FIXME|Not complete|not implemented|missing|gap|501|Open Issues|in progress" specs/progress.md specs/*.md app routes tests database -S


rg -n "partial|deferred|TODO|FIXME|Not complete|not implemented|missing|gap|501|Open Issues|in progress" specs/progress.md specs/*.md app routes tests database -S

rg -n 'partial|deferred|TODO|FIXME|Not complete|not implemented|missing|gap|501|Open Issues|in progress' specs/progress.md 'specs/*.md' app routes tests database -S

Question: partial|deferred|TODO|FIXME|Not complete|not implemented|missing|gap|501|Open Issues|in progress

specs/progress.md:9:- Status: in progress
specs/progress.md:29:| Database schema | `specs/01-DATABASE-SCHEMA.md` | partial | Phase 1 tenancy/auth tables, Phase 2 catalog tables, Phase 3 theme/content/navigation tables including `navigation_items.parent_id` nesting, Phase 4 cart/checkout/pricing tables, Phase 5 order/payment/fulfillment tables, and Phase 7 search/analytics/apps/webhooks/export tables are implemented: search_settings, search_queries, SQLite FTS5 `products_fts`, analytics_events, analytics_daily, apps, app_installations, oauth_clients, oauth_tokens, webhook_subscriptions, webhook_deliveries, and data_exports. |
specs/progress.md:30:| Routes/API | `specs/02-API-ROUTES.md` | partial | Phase 1 auth routes plus catalog/cart/order/customer/discount/content/settings/search/analytics/apps/developers UI routes exist: `/`, `/collections`, `/collections/{handle}`, `/products/{handle}`, `/cart`, `/checkout`, `/checkout/confirmation/{order}`, `/search`, `/pages/{handle}`, `/account/login`, `/account/register`, `/forgot-password`, `/reset-password/{token}`, `/account/forgot-password`, `/account/reset-password/{token}`, `/account`, `/account/orders`, `/account/orders/{order}`, `/account/addresses`, `/account/logout`, `/admin/login`, `/admin/forgot-password`, `/admin/reset-password/{token}`, `/admin/products`, `/admin/products/create`, `/admin/products/{product}/edit`, `/admin/collections`, `/admin/collections/create`, `/admin/collections/{collection}/edit`, `/admin/inventory`, `/admin/orders`, `/admin/orders/{order}`, `/admin/customers`, `/admin/customers/{customer}`, `/admin/discounts`, `/admin/discounts/create`, `/admin/discounts/{discount}/edit`, `/admin/pages`, `/admin/pages/create`, `/admin/pages/{page}/edit`, `/admin/navigation`, `/admin/themes`, `/admin/themes/{theme}/editor`, `/admin/settings`, `/admin/settings/shipping`, `/admin/settings/taxes`, `/admin/settings/checkout`, `/admin/settings/notifications`, `/admin/search/settings`, `/admin/analytics`, `/admin/apps`, `/admin/apps/{installation}`, and `/admin/developers`. Storefront REST API now includes cart line CRUD, checkout address/shipping/discount/payment-method selection, checkout payment completion, token-gated order lookup, product search, search suggestions, and analytics event ingestion under `/api/storefront/v1`. Admin REST API now includes platform organization/store creation, store invite and membership endpoints, store-scoped product list/detail/create/update/archive/media presign, customer/order list/detail, collection CRUD, discount CRUD, content page CRUD, store settings read/update, search status/reindex endpoints, analytics summary, order export creation/status, shipping zone/rate settings, tax settings read/update, theme upload/publish/settings update, fulfillment creation, and refund creation under `/api/admin/v1/stores/{store}` with session or scoped bearer-token authentication. `/oauth/authorize`, `/oauth/token`, and `/api/apps/v1/stores/{store}` app routes are registered as explicit `501 Not Implemented` stubs per the deferred OAuth/app ecosystem scope note. |
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:32:| Storefront UI | `specs/04-STOREFRONT-UI.md` | partial | Storefront layout with desktop navigation and mobile hamburger flyout, home, collections index/detail, product detail, SQLite-backed search with filters/sort/pagination, breadcrumbs, price display, product cards, DB-backed pages, seeded navigation menus with one-level dropdown children, cached theme settings, product add-to-cart persistence, cart drawer, cart page with discount and shipping estimates plus stock-limit feedback, checkout address/shipping/discount/payment-selection/order-submission UI, order confirmation, customer password reset forms at the spec root paths with account-path aliases, customer account overview/navigation/logout, customer order list/detail views, and customer address book list/create/update/default/delete controls render seeded and runtime data. |
specs/progress.md:33:| Business logic | `specs/05-BUSINESS-LOGIC.md` | partial | `ResolveStore`, `BelongsToStore`, `StoreScope`, role checks, customer guard provider, store-scoped `CustomerPasswordResetService`, catalog product lifecycle transi…30897 chars truncated…ss fields]
app/Http/Controllers/Api/Apps/V1/DeferredEndpointController.php:13:            'message' => 'App API endpoints are deferred for initial implementation.',
app/Http/Controllers/Api/Apps/V1/DeferredEndpointController.php:14:        ], 501);
database/factories/OrderFactory.php:113:    public function partiallyFulfilled(): static
app/Jobs/ProcessMediaUpload.php:78:            throw new RuntimeException('Media file is missing from public storage.');
tests/Feature/Orders/RefundServiceTest.php:70:test('refund service processes a partial line refund and restocks inventory', function () {
app/Http/Controllers/Api/Admin/V1/ThemeController.php:85:        $missing = array_values(array_diff(ThemeArchiveInstaller::requiredPaths(), $paths));
app/Http/Controllers/Api/Admin/V1/ThemeController.php:87:        if ($missing !== []) {
app/Http/Controllers/Api/Admin/V1/ThemeController.php:89:                'theme' => __('The theme is missing required file: :path', ['path' => $missing[0]]),
tests/Feature/Orders/FulfillmentServiceTest.php:67:test('fulfillment service creates partial and complete fulfillments', function () {
app/Http/Controllers/Api/OAuthController.php:23:            'message' => 'OAuth app ecosystem endpoints are deferred for initial implementation.',
app/Http/Controllers/Api/OAuthController.php:24:        ], 501);
app/Http/Controllers/Api/Admin/V1/OrderController.php:22:            'financial_status' => ['nullable', Rule::in(['pending', 'authorized', 'paid', 'partially_refunded', 'refunded', 'voided'])],
app/Http/Controllers/Api/Admin/V1/OrderController.php:23:            'fulfillment_status' => ['nullable', Rule::in(['unfulfilled', 'partial', 'fulfilled'])],
database/factories/ShippingRateFactory.php:37:                    ['min_g' => 501, 'max_g' => 2000, 'amount' => 899],
database/factories/ShippingRateFactory.php:50:                    ['min_amount' => 7501, 'amount' => 0],
app/Services/CartService.php:261:            throw InvalidCartOperationException::because('Variant inventory is missing.');
app/Http/Controllers/Api/Admin/V1/OrderExportController.php:24:            'filters.financial_status' => ['nullable', Rule::in(['pending', 'authorized', 'paid', 'partially_refunded', 'refunded', 'voided'])],
app/Http/Controllers/Api/Admin/V1/OrderExportController.php:25:            'filters.fulfillment_status' => ['nullable', Rule::in(['unfulfilled', 'partial', 'fulfilled'])],
app/Services/ThemeArchiveInstaller.php:236:        $missing = array_values(array_diff(self::REQUIRED_PATHS, array_keys($files)));
app/Services/ThemeArchiveInstaller.php:238:        if ($missing !== []) {
app/Services/ThemeArchiveInstaller.php:240:                'file' => __('The theme archive is missing required file: :path', ['path' => $missing[0]]),
tests/Feature/Api/AdminCatalogApiTest.php:337:test('deferred oauth and app ecosystem routes return not implemented', function (): void {
tests/Feature/Api/AdminCatalogApiTest.php:342:        ->assertStatus(501)
tests/Feature/Api/AdminCatalogApiTest.php:343:        ->assertJsonPath('message', 'OAuth app ecosystem endpoints are deferred for initial implementation.');
tests/Feature/Api/AdminCatalogApiTest.php:352:        ->assertStatus(501)
tests/Feature/Api/AdminCatalogApiTest.php:353:        ->assertJsonPath('message', 'OAuth app ecosystem endpoints are deferred for initial implementation.');
tests/Feature/Api/AdminCatalogApiTest.php:356:        ->assertStatus(501)
tests/Feature/Api/AdminCatalogApiTest.php:357:        ->assertJsonPath('message', 'App API endpoints are deferred for initial implementation.');
tests/Feature/Checkout/PricingServicesTest.php:271:                ['min_g' => 501, 'max_g' => 2000, 'amount' => 899],
tests/Feature/Foundation/DatabaseConstraintTest.php:21:        ->and($tableSql['orders'])->toContain('"financial_status" varchar check ("financial_status" in (\'pending\', \'authorized\', \'paid\', \'partially_refunded\', \'refunded\', \'voided\'))')
tests/Browser/Admin/OrderManagementTest.php:52:        abort(500, 'Seeded bank transfer order is missing.');
tests/Browser/Admin/OrderManagementTest.php:164:        ->fill('textarea[wire\\:model="refundReason"]', 'Customer requested partial refund')
tests/Feature/Catalog/VariantMatrixServiceTest.php:13:test('variant matrix rebuild creates missing cartesian combinations and inventory', function () {
tests/Feature/Auth/CustomerPasswordResetTest.php:158:        ->set('email', 'missing@example.test')
database/migrations/2026_05_04_000408_create_orders_table.php:22:            $table->enum('financial_status', ['pending', 'authorized', 'paid', 'partially_refunded', 'refunded', 'voided'])->default('pending');
database/migrations/2026_05_04_000408_create_orders_table.php:23:            $table->enum('fulfillment_status', ['unfulfilled', 'partial', 'fulfilled'])->default('unfulfilled');
app/Enums/FulfillmentStatus.php:8:    case Partial = 'partial';
app/Enums/FinancialStatus.php:10:    case PartiallyRefunded = 'partially_refunded';


Output:
Chunk ID: 9f00ad
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 10225
Output:
Total output lines: 160

specs/progress.md:9:- Status: in progress
specs/progress.md:29:| Database schema | `specs/01-DATABASE-SCHEMA.md` | partial | Phase 1 tenancy/auth tables, Phase 2 catalog tables, Phase 3 theme/content/navigation tables including `navigation_items.parent_id` nesting, Phase 4 cart/checkout/pricing tables, Phase 5 order/payment/fulfillment tables, and Phase 7 search/analytics/apps/webhooks/export tables are implemented: search_settings, search_queries, SQLite FTS5 `products_fts`, analytics_events, analytics_daily, apps, app_installations, oauth_clients, oauth_tokens, webhook_subscriptions, webhook_deliveries, and data_exports. |
specs/progress.md:30:| Routes/API | `specs/02-API-ROUTES.md` | partial | Phase 1 auth routes plus catalog/cart/order/customer/discount/content/settings/search/analytics/apps/developers UI routes exist: `/`, `/collections`, `/collections/{handle}`, `/products/{handle}`, `/cart`, `/checkout`, `/checkout/confirmation/{order}`, `/search`, `/pages/{handle}`, `/account/login`, `/account/register`, `/forgot-password`, `/reset-password/{token}`, `/account/forgot-password`, `/account/reset-password/{token}`, `/account`, `/account/orders`, `/account/orders/{order}`, `/account/addresses`, `/account/logout`, `/admin/login`, `/admin/forgot-password`, `/admin/reset-password/{token}`, `/admin/products`, `/admin/products/create`, `/admin/products/{product}/edit`, `/admin/collections`, `/admin/collections/create`, `/admin/collections/{collection}/edit`, `/admin/inventory`, `/admin/orders`, `/admin/orders/{order}`, `/admin/customers`, `/admin/customers/{customer}`, `/admin/discounts`, `/admin/discounts/create`, `/admin/discounts/{discount}/edit`, `/admin/pages`, `/admin/pages/create`, `/admin/pages/{page}/edit`, `/admin/navigation`, `/admin/themes`, `/admin/themes/{theme}/editor`, `/admin/settings`, `/admin/settings/shipping`, `/admin/settings/taxes`, `/admin/settings/checkout`, `/admin/settings/notifications`, `/admin/search/settings`, `/admin/analytics`, `/admin/apps`, `/admin/apps/{installation}`, and `/admin/developers`. Storefront REST API now includes cart line CRUD, checkout address/shipping/discount/payment-method selection, checkout payment completion, token-gated order lookup, product search, search suggestions, and analytics event ingestion under `/api/storefront/v1`. Admin REST API now includes platform organization/store creation, store invite and membership endpoints, store-scoped product list/detail/create/update/archive/media presign, customer/order list/detail, collection CRUD, discount CRUD, content page CRUD, store settings read/update, search status/reindex endpoints, analytics summary, order export creation/status, shipping zone/rate settings, tax settings read/update, theme upload/publish/settings update, fulfillment creation, and refund creation under `/api/admin/v1/stores/{store}` with session or scoped bearer-token authentication. `/oauth/authorize`, `/oauth/token`, and `/api/apps/v1/stores/{store}` app routes are registered as explicit `501 Not Implemented` stubs per the deferred OAuth/app ecosystem scope note. |
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:32:| Storefront UI | `specs/04-STOREFRONT-UI.md` | partial | Storefront layout with desktop navigation and mobile hamburger flyout, home, collections index/detail, product detail, SQLite-backed search with filters/sort/pagination, breadcrumbs, price display, product cards, DB-backed pages, seeded navigation menus with one-level dropdown children, cached theme settings, product add-to-cart persistence, cart drawer, cart page with discount and shipping estimates plus stock-limit feedback, checkout address/shipping/discount/payment-selection/order-submission UI, order confirmation, customer password reset forms at the spec root paths with account-path aliases, customer account overview/navigation/logout, customer order list/detail views, and customer address book list/create/update/default/delete controls render seeded and runtime data. |
specs/progress.md:33:| Business logic | `specs/05-BUSINESS-LOGIC.md` | partial | `ResolveStore`, `BelongsToStore`, `StoreScope`, role checks, customer guard provider, store-scoped `CustomerPasswordResetService`, catalog product lifecycle transitions, SKU uniqueness, variant matrix rebuilding, inventory reserve/release/commit/restock, automatic variant inventory, variant option mapping, media resize/cleanup job, `SearchService` with per-store synonym expansion and stop-word removal, product observer FTS sync, search query logging, search API rate limiting, `AnalyticsService`, analytics event deduplication, daily analytics aggregation, scheduled `AggregateAnalytics`, analytics API rate limiting, `WebhookService`, timestamped HMAC signing/verification, database-queued `DeliverWebhook`, delivery logging/retry backoff metadata, webhook circuit breaker pausing, domain-event webhook dispatch listener, developer token generation backed by `oauth_tokens`, `OrderExportService`, `CartService`, `DiscountService` including one-per-customer code discount redemption checks and automatic/code order-line attribution, `ShippingCalculator`, `TaxCalculator`, `PricingEngine`, checkout transitions through completion, checkout expiration, abandoned cart cleanup, session cart lookup, cart discount handoff to checkout, customer login cart merge, product add-to-cart mutations, storefront cart stock-limit feedback for deny-policy inventory, `PaymentService`, mock PSP, idempotent order creation, checkout UI/API order submission, session/customer/token-gated order confirmation and lookup, customer order-history scoping, customer address book management, customer logout session invalidation, admin dashboard order aggregates, admin analytics reporting, admin customer address management, admin discount rule management, admin content/theme/navigation/settings/search/apps/developers orchestration, admin order UI/API action orchestration, `RefundService`, `FulfillmentService`, bank-transfer payment confirmation, and unpaid bank-transfer cancellation are implemented. |
specs/progress.md:34:| Auth/security | `specs/06-AUTH-AND-SECURITY.md` | partial | Admin Livewire login/logout/reset, customer guard/provider, customer login/register/logout, customer password reset GET/POST routes at `/forgot-password` and `/reset-password/{token}` with store-scoped hashed tokens, reset-link throttling, generic reset responses, login rate limiting, session hardening, concrete resource policies, encrypted app client/webhook secrets, one-time display developer token generation, and a compatible hashed bearer-token admin API middleware with store/ability enforcement are implemented. |
specs/progress.md:37:| Roadmap phases | `specs/09-IMPLEMENTATION-ROADMAP.md` | in progress | Phase 1 foundation, Phase 2 catalog data/UI, Phase 3 theme/content/navigation data with storefront consumption, Phase 4 cart/checkout/pricing backend foundation, Phase 4 storefront cart/checkout UI through `payment_selected`, the Phase 4 cart/checkout REST API surface, cart-page estimates, Phase 5 order/payment backend foundation, Phase 5 refund/fulfillment services, Phase 5 storefront order completion/customer order surfaces, Phase 5 admin order management, Phase 5 order API surfaces, Phase 6 admin dashboard/customer/discount/content/settings/theme/navigation management, Phase 7 search, Phase 7 analytics, and Phase 7 apps/webhooks are implemented. Final completion audit found and closed the missing admin product write API plus platform/membership API surfaces; the audit continues. |
specs/progress.md:45:- 2026-05-03: Planning challenger sub-agent found the repo was still a starter kit and produced the acceptance checklist by vertical slice. Risks added here: missing Sanctum dependency, password_hash/Fortify coordination, route/API absence, and E2E count inconsistencies.
specs/progress.md:46:- 2026-05-03: `php artisan migrate:fresh --seed --no-interaction` passed on real `database/database.sqlite` after creating the missing SQLite file.
specs/progress.md:78:- 2026-05-03: Phase 4 explorer QA mapped the cart, checkout, discount, shipping, tax, pricing, and cleanup-job boundary; order creation/payment capture were kept for Phase 5 because order/payment tables are not implemented yet.
specs/progress.md:195:- 2026-05-04: `mcp__laravel_boost__.application_info` and schema summary reconfirmed Laravel 12.51.0/PHP 8.4/Livewire 4.1.4/Flux 2.12.0/Pest 4.3.2/SQLite and showed search/analytics/app tables were still missing before the search slice.
specs/progress.md:231:- 2026-05-04: `mcp__laravel_boost__.search_docs` consulted Laravel 12 password reset, notification, validation, and rate limiting docs plus Livewire 4, Flux UI, and Pest 4 docs before the customer password reset gap fix.
specs/progress.md:322:- 2026-05-04: `php artisan make:controller Api/Admin/V1/ProductController --no-interaction`, `php artisan make:controller Api/Admin/V1/CustomerController --no-interaction`, `php artisan make:resource Admin/V1/ProductResource --no-interaction`, `php artisan make:resource Admin/V1/CustomerResource --no-interaction`, `php artisan make:controller Api/OAuthController --no-interaction`, `php artisan make:controller Api/Apps/V1/DeferredEndpointController --no-interaction`, and `php artisan make:test Api/AdminCatalogApiTest --pest --no-interaction` created the admin catalog API controllers/resources, deferred app/OAuth controllers, and tests.
specs/progress.md:325:- 2026-05-04: `php artisan route:list --path=oauth --except-vendor` confirmed `/oauth/authorize` and `/oauth/token`; `php artisan route:list --path=api/apps/v1 --except-vendor` confirmed 3 deferred app API routes.
specs/progress.md:573:- 2026-05-04: Final audit found the Spec 02 admin product write API gap; `mcp__laravel_boost__.search_docs` consulted Laravel 12 FormRequest/API resource/JSON API testing and filesystem upload URL docs plus Pest 4 docs before closing it.
specs/progress.md:578:- 2026-05-04: Final audit found the remaining Spec 02 platform/membership API route gap; `mcp__laravel_boost__.search_docs` consulted Laravel 12 route middleware, FormRequest, transaction, and HTTP API testing docs before closing it.
specs/progress.md:617:- Admin dashboard sales and top-product metrics are derived from paid or partially refunded orders; the dedicated analytics page now reads analytics event and daily aggregate tables.
specs/progress.md:640:- OAuth/Passport app authorization remains deferred per the roadmap; `/oauth/*` and `/api/apps/v1/*` now return explicit `501 Not Implemented` responses, while the developer token UI uses the existing app installation and `oauth_tokens` schema as a self-contained local token store.
specs/progress.md:673:## Open Issues
specs/progress.md:679:Not complete until the final completion audit is closed. Phase 1 foundation, Phase 2 catalog data/UI surfaces, product media processing/admin upload controls, Phase 3 storefront theme/content/navigation data, deterministic Spec 07 customer/address/order/payment/fulfillment/refund seed data, the Phase 4 cart/checkout/pricing backend foundation, cart/checkout storefront UI through order completion, cart-page estimates, cart/checkout REST APIs, Phase 5 order/payment backend foundation, Phase 5 refund/fulfillment services, customer account order/address/logout views, admin dashboard/order/customer/discount/content/settings/theme-file/navigation/search/analytics/apps/developers management, storefront search and analytics APIs, platform/store-membership APIs, product/customer/collection/discount/content page/store settings/search/analytics/shipping/tax/theme/order/export API surfaces, admin product create/update/archive/media-presign API surfaces, deferred OAuth/app route stubs, all 18 Spec 08 automated browser suites, outbound webhook delivery foundations, spec-root customer password reset routes, and representative database CHECK constraint coverage are implemented.
specs/progress.md:9:- Status: in progress
specs/progress.md:29:| Database schema | `specs/01-DATABASE-SCHEMA.md` | partial | Phase 1 tenancy/auth tables, Phase 2 catalog tables, Phase 3 theme/content/navigation tables including `navigation_items.parent_id` nesting, Phase 4 cart/checkout/pricing tables, Phase 5 order/payment/fulfillment tables, and Phase 7 search/analytics/apps/webhooks/export tables are implemented: search_settings, search_queries, SQLite FTS5 `products_fts`, analytics_events, analytics_daily, apps, app_installations, oauth_clients, oauth_tokens, webhook_subscriptions, webhook_deliveries, and data_exports. |
specs/progress.md:30:| Routes/API | `specs/02-API-ROUTES.md` | partial | Phase 1 auth routes plus catalog/cart/order/customer/discount/content/settings/search/analytics/apps/developers UI routes exist: `/`, `/collections`, `/collections/{handle}`, `/products/{handle}`, `/cart`, `/checkout`, `/checkout/confirmation/{order}`, `/search`, `/pages/{handle}`, `/account/login`, `/account/register`, `/forgot-password`, `/reset-password/{token}`, `/account/forgot-password`, `/account/reset-password/{token}`, `/account`, `/account/orders`, `/account/orders/{order}`, `/account/addresses`, `/account/logout`, `/admin/login`, `/admin/forgot-password`, `/admin/reset-password/{token}`, `/admin/products`, `/admin/products/create`, `/admin/products/{product}/edit`, `/admin/collections`, `/admin/collections/create`, `/admin/collections/{collection}/edit`, `/admin/inventory`, `/admin/orders`, `/admin/orders/{order}`, `/admin/customers`, `/admin/customers/{customer}`, `/admin/discounts`, `/admin/discounts/create`, `/admin/discounts/{discount}/edit`, `/admin/pages`, `/admin/pages/create`, `/admin/pages/{page}/edit`, `/admin/navigation`, `/admin/themes`, `/admin/themes/{theme}/editor`, `/admin/settings`, `/admin/settings/shipping`, `/admin/settings/taxes`, `/admin/settings/checkout`, `/admin/settings/notifications`, `/admin/search/settings`, `/admin/analytics`, `/admin/apps`, `/admin/apps/{installation}`, and `/admin/developers`. Storefront REST API now includes cart line CRUD, checkout address/shipping/discount/payment-method selection, checkout payment completion, token-gated order lookup, product search, search suggestions, and analytics event ingestion under `/api/storefront/v1`. Admin REST API now includes platform organization/store creation, store invite and membership endpoints, store-scoped product list/detail/create/update/archive/media presign, customer/order list/detail, collection CRUD, discount CRUD, content page CRUD, store settings read/update, search status/reindex endpoints, analytics summary, order export creation/status, shipping zone/rate settings, tax settings read/update, theme upload/publish/settings update, fulfillment creation, and refund creation under `/api/admin/v1/stores/{store}` with session or scoped bearer-token authentication. `/oauth/authorize`, `/oauth/token`, and `/api/apps/v1/stores/{store}` app routes are registered as explicit `501 Not Implemented` stubs per the deferred OAuth/app ecosystem scope note. |
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:32:| Storefront UI | `specs/04-STOREFRONT-UI.md` | partial | Storefront layout with desktop navigation and mobile hamburger flyout, home, collections index/detail, product detail, SQLite-backed search with filters/sort/pagination, breadcrumbs, price display, product cards, DB-backed pages, seeded navigation menus with one-level dropdown children, cached theme settings, product add-to-cart persistence, cart drawer, cart page with discount and shipping estimates plus stock-limit feedback, checkout address/shipping/discount/payment-selection/order-submission UI, order confirmation, customer password reset forms at the spec root paths with account-path aliases, customer account overview/navigation/logout, customer order list/detail views, and customer address book list/create/update/default/delete controls render seeded and runtime data. |
specs/progress.md:33:| Business logic | `specs/05-BUSINESS-LOGIC.md` | partial | `ResolveStore`, `BelongsToStore`, `StoreScope`, role checks, customer guard provider, store-scoped `CustomerPasswordResetService`, catalog product lifecycle transitions, SKU uniqueness, variant matrix rebuilding, inventory reserve/release/commit/restock, automatic variant inventory, variant option mapping, media resize/cleanup job, `SearchService` with per-store synonym expansion and stop-word removal, product observer FTS sync, search query logging, search API rate limiting, `AnalyticsService`, analytics event deduplication, daily analytics aggregation, scheduled `AggregateAnalytics`, analytics API rate limiting, `WebhookService`, timestamped HMAC signing/verification, database-queued `DeliverWebhook`, delivery logging/retry backoff metadata, webhook circuit breaker pausing, domain-event webhook dispatch listener, developer token generation backed by `oauth_tokens`, `OrderExportService`, `CartService`, `DiscountService` including one-per-customer code discount redemption checks and automatic/code order-line attribution, `ShippingCalculator`, `TaxCalculator`, `PricingEngine`, checkout transitions through completion, checkout expiration, abandoned cart cleanup, session cart lookup, cart discount handoff to checkout, customer login cart merge, product add-to-cart mutations, storefront cart stock-limit feedback for deny-policy inventory, `PaymentService`, mock PSP, idempotent order creation, checkout UI/API order submission, session/customer/token-gated order confirmation and lookup, customer order-history scoping, customer address book management, customer logout session invalidation, admin dashboard order aggregates, admin analytics reporting, admin customer address management, admin discount rule management, admin content/theme/navigation/settings/search/apps/developers orchestration, admin order UI/API action orchestration, `RefundService`, `FulfillmentService`, bank-transfer payment confirmation, and unpaid bank-transfer cancellation are implemented. |
specs/progress.md:34:| Auth/security | `specs/06-AUTH-AND-SECURITY.md` | partial | Admin Livewire login/logout/reset, customer guard/provider, customer login/register/logout, customer password reset GET/POST routes at `/forgot-password` and `/reset-password/{token}` with store-scoped hashed tokens, reset-link throttling, generic reset responses, login rate limiting, session hardening, concrete resource policies, encrypted app client/webhook secrets, one-time display de…225 tokens truncated…ion audit found and closed the missing admin product write API plus platform/membership API surfaces; the audit continues. |
specs/progress.md:45:- 2026-05-03: Planning challenger sub-agent found the repo was still a starter kit and produced the acceptance checklist by vertical slice. Risks added here: missing Sanctum dependency, password_hash/Fortify coordination, route/API absence, and E2E count inconsistencies.
specs/progress.md:46:- 2026-05-03: `php artisan migrate:fresh --seed --no-interaction` passed on real `database/database.sqlite` after creating the missing SQLite file.
specs/progress.md:78:- 2026-05-03: Phase 4 explorer QA mapped the cart, checkout, discount, shipping, tax, pricing, and cleanup-job boundary; order creation/payment capture were kept for Phase 5 because order/payment tables are not implemented yet.
specs/progress.md:195:- 2026-05-04: `mcp__laravel_boost__.application_info` and schema summary reconfirmed Laravel 12.51.0/PHP 8.4/Livewire 4.1.4/Flux 2.12.0/Pest 4.3.2/SQLite and showed search/analytics/app tables were still missing before the search slice.
specs/progress.md:231:- 2026-05-04: `mcp__laravel_boost__.search_docs` consulted Laravel 12 password reset, notification, validation, and rate limiting docs plus Livewire 4, Flux UI, and Pest 4 docs before the customer password reset gap fix.
specs/progress.md:322:- 2026-05-04: `php artisan make:controller Api/Admin/V1/ProductController --no-interaction`, `php artisan make:controller Api/Admin/V1/CustomerController --no-interaction`, `php artisan make:resource Admin/V1/ProductResource --no-interaction`, `php artisan make:resource Admin/V1/CustomerResource --no-interaction`, `php artisan make:controller Api/OAuthController --no-interaction`, `php artisan make:controller Api/Apps/V1/DeferredEndpointController --no-interaction`, and `php artisan make:test Api/AdminCatalogApiTest --pest --no-interaction` created the admin catalog API controllers/resources, deferred app/OAuth controllers, and tests.
specs/progress.md:325:- 2026-05-04: `php artisan route:list --path=oauth --except-vendor` confirmed `/oauth/authorize` and `/oauth/token`; `php artisan route:list --path=api/apps/v1 --except-vendor` confirmed 3 deferred app API routes.
specs/progress.md:573:- 2026-05-04: Final audit found the Spec 02 admin product write API gap; `mcp__laravel_boost__.search_docs` consulted Laravel 12 FormRequest/API resource/JSON API testing and filesystem upload URL docs plus Pest 4 docs before closing it.
specs/progress.md:578:- 2026-05-04: Final audit found the remaining Spec 02 platform/membership API route gap; `mcp__laravel_boost__.search_docs` consulted Laravel 12 route middleware, FormRequest, transaction, and HTTP API testing docs before closing it.
specs/progress.md:617:- Admin dashboard sales and top-product metrics are derived from paid or partially refunded orders; the dedicated analytics page now reads analytics event and daily aggregate tables.
specs/progress.md:640:- OAuth/Passport app authorization remains deferred per the roadmap; `/oauth/*` and `/api/apps/v1/*` now return explicit `501 Not Implemented` responses, while the developer token UI uses the existing app installation and `oauth_tokens` schema as a self-contained local token store.
specs/progress.md:673:## Open Issues
specs/progress.md:679:Not complete until the final completion audit is closed. Phase 1 foundation, Phase 2 catalog data/UI surfaces, product media processing/admin upload controls, Phase 3 storefront theme/content/navigation data, deterministic Spec 07 customer/address/order/payment/fulfillment/refund seed data, the Phase 4 cart/checkout/pricing backend foundation, cart/checkout storefront UI through order completion, cart-page estimates, cart/checkout REST APIs, Phase 5 order/payment backend foundation, Phase 5 refund/fulfillment services, customer account order/address/logout views, admin dashboard/order/customer/discount/content/settings/theme-file/navigation/search/analytics/apps/developers management, storefront search and analytics APIs, platform/store-membership APIs, product/customer/collection/discount/content page/store settings/search/analytics/shipping/tax/theme/order/export API surfaces, admin product create/update/archive/media-presign API surfaces, deferred OAuth/app route stubs, all 18 Spec 08 automated browser suites, outbound webhook delivery foundations, spec-root customer password reset routes, and representative database CHECK constraint coverage are implemented.
specs/04-STOREFRONT-UI.md:289:| Grid | 2 columns on mobile, 4 columns on large screens, with gap spacing |
specs/04-STOREFRONT-UI.md:326:| Grid | 2 columns on mobile, 3 on tablet, 4 on desktop, with gap spacing |
specs/04-STOREFRONT-UI.md:352:| Form | Horizontal layout with gap. Input takes remaining width, button shrinks to fit |
specs/04-STOREFRONT-UI.md:601:| Thumbnail strip | Horizontal row with overflow scroll, small gap between thumbnails |
specs/04-STOREFRONT-UI.md:731:- Horizontal wrapping row with gap
specs/04-STOREFRONT-UI.md:1107:No additional form fields. The button text changes to "Place order - {total}" (not "Pay now", since payment is deferred).
specs/04-STOREFRONT-UI.md:1236:| Layout | Centered row with gap |
specs/04-STOREFRONT-UI.md:1265:| Form | Vertical stack with gap |
specs/04-STOREFRONT-UI.md:1297:| Form | Vertical stack with gap |
specs/01-DATABASE-SCHEMA.md:763:| financial_status | TEXT | No | 'pending' | CHECK IN ('pending', 'authorized', 'paid', 'partially_refunded', 'refunded', 'voided') | Payment status |
specs/01-DATABASE-SCHEMA.md:764:| fulfillment_status | TEXT | No | 'unfulfilled' | CHECK IN ('unfulfilled', 'partial', 'fulfilled') | Shipping status |
specs/01-DATABASE-SCHEMA.md:908:**Notes:** An order with multiple line items may be partially fulfilled. This table tracks how many units of each line item are included in each shipment.
specs/02-API-ROUTES.md:451:    else Bank Transfer (deferred)
specs/02-API-ROUTES.md:657:- `422` - Validation errors (missing required fields, invalid country code, address not in any shipping zone)
specs/02-API-ROUTES.md:909:**Success response (Bank Transfer - deferred payment):** `200 OK`
specs/02-API-ROUTES.md:937:- `422` - Validation errors (missing card fields, invalid payment method)
specs/02-API-ROUTES.md:1012:- `401` - Invalid or missing token
specs/02-API-ROUTES.md:1402:| PUT | `/api/admin/v1/stores/{storeId}/products/{productId}` | `write-products` | Update a product (partial updates supported) | `api.admin` (60/min) |
specs/02-API-ROUTES.md:1543:- `422` - Validation errors (duplicate SKU, duplicate handle, missing required fields)
specs/02-API-ROUTES.md:1647:Update a product. Supports partial updates.
specs/02-API-ROUTES.md:1651:**Example partial update request:**
specs/02-API-ROUTES.md:1829:Same fields as POST (all optional). Supports partial updates. Use `product_ids` to replace the full product set, or `add_product_ids` / `remove_product_ids` for incremental changes.
specs/02-API-ROUTES.md:1869:| POST | `/api/admin/v1/stores/{storeId}/orders/{orderId}/refunds` | `write-orders` | Create a refund (partial or full) | `api.admin` (60/min) |
specs/02-API-ROUTES.md:1880:| `financial_status` | string | No | Filter: `pending`, `paid`, `partially_refunded`, `refunded`. |
specs/02-API-ROUTES.md:1881:| `fulfillment_status` | string | No | Filter: `unfulfilled`, `partial`, `fulfilled`. |
specs/02-API-ROUTES.md:2052:    "id": 501,
specs/02-API-ROUTES.md:2079:Create a refund. Supports partial refunds by line or amount.
specs/02-API-ROUTES.md:2245:Update a discount. Supports partial updates.
specs/02-API-ROUTES.md:2531:- `422` - Invalid theme archive (missing required templates, invalid manifest)
specs/02-API-ROUTES.md:2682:Same fields as POST. Supports partial updates.
specs/02-API-ROUTES.md:2738:- `409` - Reindex already in progress
specs/02-API-ROUTES.md:2905:> **Scope note:** OAuth and app ecosystem endpoints are deferred for initial implementation. The route structure below is defined for future use but implementations should return 501 Not Implemented.
specs/02-API-ROUTES.md:3054:- **Web (admin):** Reads `store_id` from session. If missing, redirects to store selector (for platform-level users with access to multiple stores).
specs/02-API-ROUTES.md:3145:    payment_selected --> completed : POST /checkouts/{id}/pay (bank_transfer: deferred, order created as pending)
specs/02-API-ROUTES.md:3161:| `payment_selected` | `completed` | POST /checkouts/{id}/pay - Mock PSP processes payment in-process (instant for card/paypal, deferred for bank_transfer) |
specs/07-SEEDERS-AND-TEST-DATA.md:387:| `partiallyFulfilled` | fulfillment_status = "partial" |
specs/07-SEEDERS-AND-TEST-DATA.md:627:| `weightBased` | type = "weight", config_json has ranges array with tiers: 0-500g = 399, 501-2000g = 699, 2001g+ = 1299 |
specs/07-SEEDERS-AND-TEST-DATA.md:1560:| status / financial / fulfillment | paid / paid / partial |
specs/07-SEEDERS-AND-TEST-DATA.md:1675:| status / financial / fulfillment | paid / partially_refunded / fulfilled |
specs/07-SEEDERS-AND-TEST-DATA.md:1691:Refund (partial - for Graphic Print Tee only): amount 2999, reason "Item returned", status "processed", provider_refund_id "mock_re_test_order1008"
specs/07-SEEDERS-AND-TEST-DATA.md:1859:| #1003 | Jane Smith | credit_card | paid | paid | partial | 11997 | Partial fulfillment |
specs/07-SEEDERS-AND-TEST-DATA.md:1864:| #1008 | David Lee | credit_card | paid | partially_refunded | fulfilled | 8997 | Partial refund |
specs/07-SEEDERS-AND-TEST-DATA.md:2039:| Day 0 (today, partial) | 80-110 | 2-4 | 15000-30000 |
specs/05-BUSINESS-LOGIC.md:992:    else Bank Transfer (deferred)
specs/05-BUSINESS-LOGIC.md:1107:| `partially_refunded` | Some items or partial amount refunded |
specs/05-BUSINESS-LOGIC.md:1116:| `partial` | Some items shipped, others pending |
specs/05-BUSINESS-LOGIC.md:1158:           SET order.financial_status = 'partially_refunded'
specs/05-BUSINESS-LOGIC.md:1173:**CRITICAL:** A fulfillment cannot be created unless the order's `financial_status` is `paid` or `partially_refunded`. This prevents shipping goods before payment is confirmed.
specs/05-BUSINESS-LOGIC.md:1180:| `partially_refunded` | **Yes** | Payment confirmed, partial refund does not block shipment |
specs/05-BUSINESS-LOGIC.md:1196:    1. GUARD: Verify order.financial_status IN ('paid', 'partially_refunded')
specs/05-BUSINESS-LOGIC.md:1218:           SET order.fulfillment_status = 'partial'
specs/05-BUSINESS-LOGIC.md:1685:| FinancialStatus | Pending (`pending`), Authorized (`authorized`), Paid (`paid`), PartiallyRefunded (`partially_refunded`), Refunded (`refunded`), Voided (`voided`) |
specs/05-BUSINESS-LOGIC.md:1686:| FulfillmentOrderStatus | Unfulfilled (`unfulfilled`), Partial (`partial`), Fulfilled (`fulfilled`) |
specs/09-IMPLEMENTATION-ROADMAP.md:315:| `rebuildMatrix(Product $product): void` | Computes cartesian product, creates missing variants, archives orphaned ones |
specs/09-IMPLEMENTATION-ROADMAP.md:668:| `charge(Checkout $checkout, PaymentMethod $method, array $details): PaymentResult` | Processes payment in-process. For credit_card: validates magic card numbers, returns success/decline. For paypal: always succeeds. For bank_transfer: returns pending (deferred capture). |
specs/09-IMPLEMENTATION-ROADMAP.md:675:- Bank transfer: deferred capture, returns PaymentResult with status=pending
specs/09-IMPLEMENTATION-ROADMAP.md:716:- Partial refunds: `financial_status` -> `partially_refunded`
specs/09-IMPLEMENTATION-ROADMAP.md:726:| `create(Order $order, array $lines, ?array $tracking): Fulfillment` | Creates fulfillment with specified lines and quantities. **Fulfillment guard:** Throws exception if `financial_status` is not `paid` or `partially_refunded`. |
specs/09-IMPLEMENTATION-ROADMAP.md:730:Updates `order.fulfillment_status` based on whether all lines are fulfilled (`fulfilled`) or some (`partial`).
specs/09-IMPLEMENTATION-ROADMAP.md:732:**Fulfillment guard:** Before creating a fulfillment, the service checks that the order's `financial_status` is `paid` or `partially_refunded`. If the guard fails, a `FulfillmentGuardException` is thrown. This prevents shipping items before payment is confirmed (especially important for bank transfer orders).
specs/09-IMPLEMENTATION-ROADMAP.md:826:Refund modal: enter amount (partial or full), reason, restock checkbox, submit.
specs/09-IMPLEMENTATION-ROADMAP.md:1082:| calculates a weight-based rate | rate type: weight, config: ranges 0-500g=499, 501-2000g=899; cart total weight: 750g | shipping = 899 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1329:| rejects address transition with missing required fields | set address without city | validation error, status remains started | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1372:| calculates weight-based rate correctly | cart total weight 750g, rate range 501-2000g = 899 | shipping = 899 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1409:| creates a partial refund | order total 5000; refund 2000 | financial_status = partially_refunded | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1423:| updates order fulfillment status to partial | fulfill only some lines | order.fulfillment_status = partial | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1431:| fulfillment guard allows fulfillment when financial_status is partially_refunded | order with financial_status=partially_refunded | fulfillment created successfully | edge case |
specs/09-IMPLEMENTATION-ROADMAP.md:1497:| validates required address fields | submit with missing address1 | validation error | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1660:| validates required address fields | PUT address with missing city | 422 response | validation |
specs/03-ADMIN-UI.md:920:| Fulfillment status | Flux badge: unfulfilled=zinc, partial=yellow, fulfilled=green |
specs/03-ADMIN-UI.md:1019:- "Refund" button (ghost variant, shown when order is paid or partially_refunded)
specs/03-ADMIN-UI.md:1023:A Flux `callout` (variant: warning) is displayed when the order's `financial_status` is NOT `paid` and NOT `partially_refunded`:
specs/03-ADMIN-UI.md:2146:| `isExporting` | `bool` | Whether an export is in progress |
specs/03-ADMIN-UI.md:2224:| `isReindexing` | `bool` | Whether a reindex is in progress |
specs/08-PLAYWRIGHT-E2E-PLAN.md:176:**Purpose:** Hit every major public and admin page, assert they load without JavaScript errors. This is the fastest way to catch broken routes, missing views, or uncaught JS exceptions.
specs/08-PLAYWRIGHT-E2E-PLAN.md:688:        paid --> partially_refunded: partial refund
specs/08-PLAYWRIGHT-E2E-PLAN.md:692:        unfulfilled --> partial: partial fulfillment
specs/08-PLAYWRIGHT-E2E-PLAN.md:694:        partial --> fulfilled: remaining items fulfilled
specs/08-PLAYWRIGHT-E2E-PLAN.md:804:7. Enter reason: `Customer requested partial refund`
specs/08-PLAYWRIGHT-E2E-PLAN.md:1711:    Step2 -->|missing fields| Step2Error[Validation errors on address fields]
app/Http/Controllers/Api/Apps/V1/DeferredEndpointController.php:13:            'message' => 'App API endpoints are deferred for initial implementation.',
app/Http/Controllers/Api/Apps/V1/DeferredEndpointController.php:14:        ], 501);
database/factories/OrderFactory.php:113:    public function partiallyFulfilled(): static
app/Jobs/ProcessMediaUpload.php:78:            throw new RuntimeException('Media file is missing from public storage.');
tests/Feature/Orders/RefundServiceTest.php:70:test('refund service processes a partial line refund and restocks inventory', function () {
app/Http/Controllers/Api/Admin/V1/ThemeController.php:85:        $missing = array_values(array_diff(ThemeArchiveInstaller::requiredPaths(), $paths));
app/Http/Controllers/Api/Admin/V1/ThemeController.php:87:        if ($missing !== []) {
app/Http/Controllers/Api/Admin/V1/ThemeController.php:89:                'theme' => __('The theme is missing required file: :path', ['path' => $missing[0]]),
tests/Feature/Orders/FulfillmentServiceTest.php:67:test('fulfillment service creates partial and complete fulfillments', function () {
app/Http/Controllers/Api/OAuthController.php:23:            'message' => 'OAuth app ecosystem endpoints are deferred for initial implementation.',
app/Http/Controllers/Api/OAuthController.php:24:        ], 501);
app/Http/Controllers/Api/Admin/V1/OrderController.php:22:            'financial_status' => ['nullable', Rule::in(['pending', 'authorized', 'paid', 'partially_refunded', 'refunded', 'voided'])],
app/Http/Controllers/Api/Admin/V1/OrderController.php:23:            'fulfillment_status' => ['nullable', Rule::in(['unfulfilled', 'partial', 'fulfilled'])],
database/factories/ShippingRateFactory.php:37:                    ['min_g' => 501, 'max_g' => 2000, 'amount' => 899],
database/factories/ShippingRateFactory.php:50:                    ['min_amount' => 7501, 'amount' => 0],
app/Services/CartService.php:261:            throw InvalidCartOperationException::because('Variant inventory is missing.');
app/Http/Controllers/Api/Admin/V1/OrderExportController.php:24:            'filters.financial_status' => ['nullable', Rule::in(['pending', 'authorized', 'paid', 'partially_refunded', 'refunded', 'voided'])],
app/Http/Controllers/Api/Admin/V1/OrderExportController.php:25:            'filters.fulfillment_status' => ['nullable', Rule::in(['unfulfilled', 'partial', 'fulfilled'])],
app/Services/ThemeArchiveInstaller.php:236:        $missing = array_values(array_diff(self::REQUIRED_PATHS, array_keys($files)));
app/Services/ThemeArchiveInstaller.php:238:        if ($missing !== []) {
app/Services/ThemeArchiveInstaller.php:240:                'file' => __('The theme archive is missing required file: :path', ['path' => $missing[0]]),
tests/Feature/Api/AdminCatalogApiTest.php:337:test('deferred oauth and app ecosystem routes return not implemented', function (): void {
tests/Feature/Api/AdminCatalogApiTest.php:342:        ->assertStatus(501)
tests/Feature/Api/AdminCatalogApiTest.php:343:        ->assertJsonPath('message', 'OAuth app ecosystem endpoints are deferred for initial implementation.');
tests/Feature/Api/AdminCatalogApiTest.php:352:        ->assertStatus(501)
tests/Feature/Api/AdminCatalogApiTest.php:353:        ->assertJsonPath('message', 'OAuth app ecosystem endpoints are deferred for initial implementation.');
tests/Feature/Api/AdminCatalogApiTest.php:356:        ->assertStatus(501)
tests/Feature/Api/AdminCatalogApiTest.php:357:        ->assertJsonPath('message', 'App API endpoints are deferred for initial implementation.');
tests/Feature/Checkout/PricingServicesTest.php:271:                ['min_g' => 501, 'max_g' => 2000, 'amount' => 899],
tests/Feature/Foundation/DatabaseConstraintTest.php:21:        ->and($tableSql['orders'])->toContain('"financial_status" varchar check ("financial_status" in (\'pending\', \'authorized\', \'paid\', \'partially_refunded\', \'refunded\', \'voided\'))')
tests/Browser/Admin/OrderManagementTest.php:52:        abort(500, 'Seeded bank transfer order is missing.');
tests/Browser/Admin/OrderManagementTest.php:164:        ->fill('textarea[wire\\:model="refundReason"]', 'Customer requested partial refund')
tests/Feature/Catalog/VariantMatrixServiceTest.php:13:test('variant matrix rebuild creates missing cartesian combinations and inventory', function () {
tests/Feature/Auth/CustomerPasswordResetTest.php:158:        ->set('email', 'missing@example.test')
database/migrations/2026_05_04_000408_create_orders_table.php:22:            $table->enum('financial_status', ['pending', 'authorized', 'paid', 'partially_refunded', 'refunded', 'voided'])->default('pending');
database/migrations/2026_05_04_000408_create_orders_table.php:23:            $table->enum('fulfillment_status', ['unfulfilled', 'partial', 'fulfilled'])->default('unfulfilled');
app/Enums/FulfillmentStatus.php:8:    case Partial = 'partial';
app/Enums/FinancialStatus.php:10:    case PartiallyRefunded = 'partially_refunded';


varchar

varchar

varchar

varchar

Question: text


sed -n '1140,1195p' specs/01-DATABASE-SCHEMA.md


sed -n '1140,1195p' specs/01-DATABASE-SCHEMA.md

|---|---|---|---|---|---|
| id | TEXT | No | - | PRIMARY KEY | Batch UUID |
| name | TEXT | No | - | - | Batch name |
| total_jobs | INTEGER | No | - | - | Total jobs in batch |
| pending_jobs | INTEGER | No | - | - | Pending count |
| failed_jobs | INTEGER | No | - | - | Failed count |
| failed_job_ids | TEXT | No | - | - | Serialized list of failed IDs |
| options | TEXT | Yes | NULL | - | Serialized batch options |
| cancelled_at | INTEGER | Yes | NULL | - | Unix timestamp |
| created_at | INTEGER | No | - | - | Unix timestamp |
| finished_at | INTEGER | Yes | NULL | - | Unix timestamp |

### failed_jobs

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| id | INTEGER | No | autoincrement | PRIMARY KEY | |
| uuid | TEXT | No | - | UNIQUE | Job UUID |
| connection | TEXT | No | - | - | Queue connection name |
| queue | TEXT | No | - | - | Queue name |
| payload | TEXT | No | - | - | Serialized job payload |
| exception | TEXT | No | - | - | Exception trace |
| failed_at | TEXT | No | CURRENT_TIMESTAMP | - | ISO-8601 timestamp |

### personal_access_tokens

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| id | INTEGER | No | autoincrement | PRIMARY KEY | |
| tokenable_type | TEXT | No | - | - | Polymorphic type |
| tokenable_id | INTEGER | No | - | - | Polymorphic ID |
| name | TEXT | No | - | - | Token name |
| token | TEXT | No | - | UNIQUE | SHA-256 hash of the token |
| abilities | TEXT | Yes | NULL | - | JSON array of abilities |
| last_used_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
| expires_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |

### password_reset_tokens

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| email | TEXT | No | - | PRIMARY KEY | Admin user email |
| token | TEXT | No | - | - | Hashed reset token |
| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |

---

### customer_password_reset_tokens

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| store_id | INTEGER | No | - | FK -> stores(id) ON DELETE CASCADE | Owning store |
| email | TEXT | No | - | - | Customer email |
| token | TEXT | No | - | - | Hashed reset token |


Output:
Chunk ID: 01bf32
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 594
Output:
|---|---|---|---|---|---|
| id | TEXT | No | - | PRIMARY KEY | Batch UUID |
| name | TEXT | No | - | - | Batch name |
| total_jobs | INTEGER | No | - | - | Total jobs in batch |
| pending_jobs | INTEGER | No | - | - | Pending count |
| failed_jobs | INTEGER | No | - | - | Failed count |
| failed_job_ids | TEXT | No | - | - | Serialized list of failed IDs |
| options | TEXT | Yes | NULL | - | Serialized batch options |
| cancelled_at | INTEGER | Yes | NULL | - | Unix timestamp |
| created_at | INTEGER | No | - | - | Unix timestamp |
| finished_at | INTEGER | Yes | NULL | - | Unix timestamp |

### failed_jobs

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| id | INTEGER | No | autoincrement | PRIMARY KEY | |
| uuid | TEXT | No | - | UNIQUE | Job UUID |
| connection | TEXT | No | - | - | Queue connection name |
| queue | TEXT | No | - | - | Queue name |
| payload | TEXT | No | - | - | Serialized job payload |
| exception | TEXT | No | - | - | Exception trace |
| failed_at | TEXT | No | CURRENT_TIMESTAMP | - | ISO-8601 timestamp |

### personal_access_tokens

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| id | INTEGER | No | autoincrement | PRIMARY KEY | |
| tokenable_type | TEXT | No | - | - | Polymorphic type |
| tokenable_id | INTEGER | No | - | - | Polymorphic ID |
| name | TEXT | No | - | - | Token name |
| token | TEXT | No | - | UNIQUE | SHA-256 hash of the token |
| abilities | TEXT | Yes | NULL | - | JSON array of abilities |
| last_used_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
| expires_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |

### password_reset_tokens

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| email | TEXT | No | - | PRIMARY KEY | Admin user email |
| token | TEXT | No | - | - | Hashed reset token |
| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |

---

### customer_password_reset_tokens

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| store_id | INTEGER | No | - | FK -> stores(id) ON DELETE CASCADE | Owning store |
| email | TEXT | No | - | - | Customer email |
| token | TEXT | No | - | - | Hashed reset token |


The schema audit shows one stable miss from Spec 01: `personal_access_tokens` is absent. Even though the app uses the local `oauth_tokens` token store for this self-contained build, the schema spec lists the Sanctum-compatible table, so I’m adding the table and a constraint test rather than changing the auth implementation.


migration create table indexes unique morphs

database schema testing has table columns