feat(orders): implement default sorting for order queries
Added default sorting by date in descending order for order queries. Updated related views and tests to reflect this change.
This commit is contained in:
parent
0e05779f89
commit
cd7d5f5fe9
8 changed files with 164 additions and 25 deletions
|
|
@ -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<int, OrderDto> $ordersDtoPaginator */
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -98,11 +98,15 @@ class DataTableService
|
|||
* @param class-string<TModel> $modelClass
|
||||
* @param TBuilder $query
|
||||
* @param array<int, string> $ignore Columns to ignore for sorting.
|
||||
* @param array<string, string> $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');
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
'cols' => $cols,
|
||||
'mobileHide' => [],
|
||||
'ignore' => ['customer', 'pickup_date'],
|
||||
'defaultSortCol' => 'date',
|
||||
'defaultSortWay' => 'desc',
|
||||
])
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
|
|||
|
|
@ -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 @@
|
|||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="h3">
|
||||
{{ Str::ucfirst(trans_choice('models.classes.orders', \INF)) }} {{ Str::ucfirst(__('crud.actions.list')) }}
|
||||
{{ __('Today\'s Orders') }}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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<int, OrderDto> $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<int, OrderDto> $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<int, OrderDto> $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 {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue