diff --git a/app/Queries/Orders/GetBoPaginatedOrdersQuery.php b/app/Queries/Orders/GetBoPaginatedOrdersQuery.php index ff64e16..8d32b2c 100644 --- a/app/Queries/Orders/GetBoPaginatedOrdersQuery.php +++ b/app/Queries/Orders/GetBoPaginatedOrdersQuery.php @@ -68,7 +68,13 @@ final readonly class GetBoPaginatedOrdersQuery implements Query $query->where('status', $input->status); } - $this->dataTable->sortQuery(Order::class, $query); + $this->dataTable->sortQuery( + Order::class, + $query, + defaultSortCol: 'date', + defaultSortWay: \SortDirection::Descending, + columnMap: ['date' => 'id'], + ); $paginator = $this->dataTable->paginateQuery($query, $input->pagination); /** @var LengthAwarePaginator $ordersDtoPaginator */ diff --git a/app/Queries/Orders/GetBoTodayOrdersQuery.php b/app/Queries/Orders/GetBoTodayOrdersQuery.php index 5b3ba60..dbe7507 100644 --- a/app/Queries/Orders/GetBoTodayOrdersQuery.php +++ b/app/Queries/Orders/GetBoTodayOrdersQuery.php @@ -37,9 +37,15 @@ final readonly class GetBoTodayOrdersQuery implements Query $query = Order::query()->with(['shop']); - $query->where('pickup_day', Date::now()->toDateString()); + $query->whereCreatedAtBetween(Date::now(), Date::now()); - $this->dataTable->sortQuery(Order::class, $query); + $this->dataTable->sortQuery( + Order::class, + $query, + defaultSortCol: 'date', + defaultSortWay: \SortDirection::Descending, + columnMap: ['date' => 'id'], + ); $paginator = $this->dataTable->paginateQuery($query, $input->pagination); diff --git a/app/Services/DataTableService.php b/app/Services/DataTableService.php index bca2b26..dfbff08 100644 --- a/app/Services/DataTableService.php +++ b/app/Services/DataTableService.php @@ -98,11 +98,15 @@ class DataTableService * @param class-string $modelClass * @param TBuilder $query * @param array $ignore Columns to ignore for sorting. + * @param array $columnMap Map virtual column names to database columns. */ public function sortQuery( string $modelClass, Builder $query, - array $ignore = [] + array $ignore = [], + ?string $defaultSortCol = null, + SortDirection $defaultSortWay = SortDirection::Ascending, + array $columnMap = [], ): Builder { $table = (new $modelClass())->getTable(); $rName = $this->request->route()?->getName() ?? 'noroute'; @@ -115,15 +119,22 @@ class DataTableService $sort_col = $sort['sort_col']; $sort_way = $sort['sort_way']; + if ((! $sort_col || $sort_col === 'undefined') && $defaultSortCol !== null) { + $sort_col = $defaultSortCol; + $sort_way = $defaultSortWay; + } + $this->persistSortSession($rName, $sort_col, $sort_way); - if ($sort_col && $sort_col !== 'undefined' && $sort_way && ! in_array($sort_col, $ignore) && $this->hasTableColumn($table, $sort_col)) { + $dbCol = $columnMap[$sort_col] ?? $sort_col; + + if ($sort_col && $sort_col !== 'undefined' && $sort_way && ! in_array($sort_col, $ignore) && $this->hasTableColumn($table, $dbCol)) { $direction = match ($sort_way) { SortDirection::Descending => 'desc', default => 'asc', }; - $query->orderBy("{$table}.{$sort_col}", $direction); + $query->orderBy("{$table}.{$dbCol}", $direction); } elseif ($this->hasTableColumn($table, self::ORDER_COLUMN)) { $query->orderBy(self::ORDER_COLUMN, 'asc'); diff --git a/resources/views/bo/modules/tableColSorter.blade.php b/resources/views/bo/modules/tableColSorter.blade.php index 7817917..ba91692 100644 --- a/resources/views/bo/modules/tableColSorter.blade.php +++ b/resources/views/bo/modules/tableColSorter.blade.php @@ -2,9 +2,16 @@ // * Route vars $routeName = request()->route()?->getName() ?? 'noroute'; + // * Default sort vars + $defaultSortCol = $defaultSortCol ?? 'order'; + $defaultSortWay = $defaultSortWay ?? 'asc'; + $defaultSortWayStr = $defaultSortWay instanceof \SortDirection + ? ($defaultSortWay === \SortDirection::Descending ? 'desc' : 'asc') + : (string) $defaultSortWay; + // * Request vars - $currentSortCol = session()->get("{$routeName}.sort_col"); - $currentSortWay = session()->get("{$routeName}.sort_way"); + $currentSortCol = session()->get("{$routeName}.sort_col", $defaultSortCol); + $currentSortWay = session()->get("{$routeName}.sort_way", $defaultSortWayStr); $searchQuery = request()->input('search'); @@ -18,7 +25,7 @@ View::share('noOrder', $noOrder); // * Reset button - $hasCustomSort = ($currentSortCol && $currentSortCol !== 'order') || ($currentSortCol === 'order' && $currentSortWay === 'desc'); + $hasCustomSort = ($currentSortCol && $currentSortCol !== $defaultSortCol) || ($currentSortCol === $defaultSortCol && $currentSortWay !== $defaultSortWayStr); $showReset = !empty($searchQuery) || $hasCustomSort; @endphp diff --git a/resources/views/bo/orders/index-list.blade.php b/resources/views/bo/orders/index-list.blade.php index e5d1d37..3c58c7a 100644 --- a/resources/views/bo/orders/index-list.blade.php +++ b/resources/views/bo/orders/index-list.blade.php @@ -16,6 +16,8 @@ 'cols' => $cols, 'mobileHide' => [], 'ignore' => ['customer', 'pickup_date'], + 'defaultSortCol' => 'date', + 'defaultSortWay' => 'desc', ]) diff --git a/resources/views/bo/orders/index-today.blade.php b/resources/views/bo/orders/index-today.blade.php index c3fe97b..3109e86 100644 --- a/resources/views/bo/orders/index-today.blade.php +++ b/resources/views/bo/orders/index-today.blade.php @@ -1,10 +1,6 @@ @extends('bo.layouts.layout') -@if (request()->routeIs('bo.merch.starred')) - @section('title', __('crud.helpers.starred')) -@else - @section('title', Str::ucfirst(trans_choice('models.classes.orders', \INF))) -@endif +@section('title', __('Today\'s Orders')) @section('breadcrumb', request()->route()->getName()) @@ -14,7 +10,7 @@

- {{ Str::ucfirst(trans_choice('models.classes.orders', \INF)) }} {{ Str::ucfirst(__('crud.actions.list')) }} + {{ __('Today\'s Orders') }}

diff --git a/tests/Feature/App/Http/Controllers/Bo/OrdersControllerTest.php b/tests/Feature/App/Http/Controllers/Bo/OrdersControllerTest.php index aaf1e2a..5d6d26e 100644 --- a/tests/Feature/App/Http/Controllers/Bo/OrdersControllerTest.php +++ b/tests/Feature/App/Http/Controllers/Bo/OrdersControllerTest.php @@ -113,25 +113,72 @@ describe('Bo\OrdersController', function (): void { // THEN expect($response->status())->toBe(200); - $response->assertViewHas('orders', function ($orders) use ($ref): bool { - return $orders instanceof LengthAwarePaginator - && $orders->total() === 1 - && (string) $orders->first()->reference === $ref; + $response->assertViewHas('orders', function (mixed $orders) use ($ref): bool { + /** @var \Illuminate\Pagination\LengthAwarePaginator $orders */ + return $orders instanceof \Illuminate\Pagination\LengthAwarePaginator + && $orders->total() === 1 + && (string) $orders->first()?->reference === $ref; }); }); - it('can access today orders', function (): void { + it('sorts index orders by default by date from newest to oldest', function (): void { /** @var \Tests\TestCase $this */ // GIVEN - $shop = Shop::factory()->create(); - Order::forceCreate([ - 'id' => Uuid::uuid7()->toString(), + $shop = Shop::factory()->create(); + $olderOrder = Order::forceCreate([ + 'id' => Uuid::uuid7(Date::now()->subHours(2))->toString(), + 'shop_id' => $shop->id, + 'reference' => new OrderReference(Uuid::uuid7()), + 'status' => OrderStatus::VALIDATED->value, + 'total' => new Money(1000, new Currency('EUR')), + 'employee' => new OrderEmployee(new Matricule('1000198'), 'Old', 'Order', new ShopperEstablishmentCode('FAC01'), new ShopperSiteCode('SITE'), new ShopperServiceCode('101')), + 'delivery_address' => new DeliveryAddress('Shop Name', 'Address', '75000', 'Paris'), + 'pickup_day' => Date::now()->addDays(7)->toDateString(), + ]); + $newerOrder = Order::forceCreate([ + 'id' => Uuid::uuid7(Date::now())->toString(), + 'shop_id' => $shop->id, + 'reference' => new OrderReference(Uuid::uuid7()), + 'status' => OrderStatus::VALIDATED->value, + 'total' => new Money(2000, new Currency('EUR')), + 'employee' => new OrderEmployee(new Matricule('1000199'), 'New', 'Order', new ShopperEstablishmentCode('FAC01'), new ShopperSiteCode('SITE'), new ShopperServiceCode('101')), + 'delivery_address' => new DeliveryAddress('Shop Name', 'Address', '75000', 'Paris'), + 'pickup_day' => Date::now()->addDays(7)->toDateString(), + ]); + + // WHEN + $response = $this->get('/bo/orders'); + + // THEN + expect($response->status())->toBe(200); + /** @var \Illuminate\Pagination\LengthAwarePaginator $orders */ + $orders = $response->viewData('orders'); + expect($orders->first()?->id)->toBe($newerOrder->id) + ->and($orders->last()?->id)->toBe($olderOrder->id); + }); + + it('can access today orders and filters out past orders', function (): void { + /** @var \Tests\TestCase $this */ + // GIVEN + $shop = Shop::factory()->create(); + $todayOrder = Order::forceCreate([ + 'id' => Uuid::uuid7(Date::now())->toString(), 'shop_id' => $shop->id, 'reference' => new OrderReference(Uuid::uuid7()), 'status' => OrderStatus::VALIDATED->value, 'total' => new Money(3000, new Currency('EUR')), 'employee' => new OrderEmployee(new Matricule('1000103'), 'Charlie', 'Brown', new ShopperEstablishmentCode('FAC01'), new ShopperSiteCode('SITE'), new ShopperServiceCode('101')), 'delivery_address' => new DeliveryAddress('Shop Name', 'Address', '75000', 'Paris'), + 'pickup_day' => Date::now()->addDays(7)->toDateString(), + ]); + $pastOrder = Order::forceCreate([ + 'id' => Uuid::uuid7(Date::now()->subDays(3))->toString(), + 'shop_id' => $shop->id, + 'reference' => new OrderReference(Uuid::uuid7()), + 'status' => OrderStatus::VALIDATED->value, + 'total' => new Money(4000, new Currency('EUR')), + 'employee' => new OrderEmployee(new Matricule('1000104'), 'Past', 'Order', new ShopperEstablishmentCode('FAC01'), new ShopperSiteCode('SITE'), new ShopperServiceCode('101')), + 'delivery_address' => new DeliveryAddress('Shop Name', 'Address', '75000', 'Paris'), 'pickup_day' => Date::now()->toDateString(), ]); @@ -141,7 +188,10 @@ describe('Bo\OrdersController', function (): void { // THEN expect($response->status())->toBe(200); $response->assertViewIs('bo.orders.index-today'); - $response->assertViewHas('orders', fn ($orders) => $orders instanceof LengthAwarePaginator && $orders->total() >= 1); + /** @var \Illuminate\Pagination\LengthAwarePaginator $orders */ + $orders = $response->viewData('orders'); + expect($orders->total())->toBe(1) + ->and($orders->first()?->id)->toBe($todayOrder->id); }); it('can access order show detail', function (): void { @@ -215,7 +265,7 @@ describe('Bo\OrdersController', function (): void { $refreshedOrder = Order::query()->find($order->id); expect($refreshedOrder)->not->toBeNull() - ->and($refreshedOrder->status)->toBe(OrderStatus::CANCELED); + ->and($refreshedOrder?->status)->toBe(OrderStatus::CANCELED); }); it('rejects unauthenticated users', function (): void { diff --git a/tests/Unit/Services/DataTableServiceTest.php b/tests/Unit/Services/DataTableServiceTest.php index 72707df..26f6b3b 100644 --- a/tests/Unit/Services/DataTableServiceTest.php +++ b/tests/Unit/Services/DataTableServiceTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Tests\Unit\Services; +use App\Models\Order; use App\Models\Product; use App\Services\DataTableService; use Illuminate\Contracts\Database\Query\Builder; @@ -129,4 +130,64 @@ describe('DataTableService', function () { // THEN // Mockery verifies on teardown that orderBy('order', 'asc') was called once. }); + + it('sorts by custom default column and way when no sort column is specified', function () { + // GIVEN + $request = Request::create('/bo/orders', 'GET'); + $session = mock(Session::class); + $session->shouldReceive('get')->andReturn(null); + + Schema::shouldReceive('getColumnListing') + ->once() + ->with('orders') + ->andReturn(['id', 'reference', 'status', 'total']); + + $query = mock(Builder::class); + $query->shouldReceive('orderBy')->once()->with('orders.id', 'desc'); + + $service = new DataTableService($request, $session); + + // WHEN + $service->sortQuery( + Order::class, + $query, + defaultSortCol: 'date', + defaultSortWay: SortDirection::Descending, + columnMap: ['date' => 'id'], + ); + + // THEN + // Mockery verifies on teardown that orderBy('orders.id', 'desc') was called once. + }); + + it('uses columnMap when sorting by an explicit virtual column', function () { + // GIVEN + $request = Request::create('/bo/orders', 'GET', [ + 'sort_col' => 'date', + 'sort_way' => 'asc', + ]); + $session = mock(Session::class); + $session->shouldReceive('get')->andReturn(null); + $session->shouldReceive('put')->twice(); + + Schema::shouldReceive('getColumnListing') + ->once() + ->with('orders') + ->andReturn(['id', 'reference', 'status', 'total']); + + $query = mock(Builder::class); + $query->shouldReceive('orderBy')->once()->with('orders.id', 'asc'); + + $service = new DataTableService($request, $session); + + // WHEN + $service->sortQuery( + Order::class, + $query, + columnMap: ['date' => 'id'], + ); + + // THEN + // Mockery verifies on teardown that orderBy('orders.id', 'asc') was called once. + }); });