feat(category): include products from all descendant categories in paginated response
This commit is contained in:
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Domains\Commerce\Catalog\Services;
|
namespace App\Domains\Commerce\Catalog\Services;
|
||||||
|
|
||||||
use App\Shared\Attachable\Models\Attachment;
|
|
||||||
use App\Shared\Attachable\Services\AttachmentService;
|
|
||||||
use App\Domains\Commerce\Catalog\Enums\CatalogItemType;
|
use App\Domains\Commerce\Catalog\Enums\CatalogItemType;
|
||||||
use App\Domains\Commerce\Catalog\Models\Attribute;
|
use App\Domains\Commerce\Catalog\Models\Attribute;
|
||||||
use App\Domains\Commerce\Catalog\Models\CatalogItem;
|
use App\Domains\Commerce\Catalog\Models\CatalogItem;
|
||||||
@@ -11,8 +9,10 @@ use App\Domains\Commerce\Catalog\Models\Category;
|
|||||||
use App\Domains\Commerce\Catalog\Models\Inventory;
|
use App\Domains\Commerce\Catalog\Models\Inventory;
|
||||||
use App\Domains\Commerce\Catalog\Models\ItemAttribute;
|
use App\Domains\Commerce\Catalog\Models\ItemAttribute;
|
||||||
use App\Domains\Commerce\Catalog\Models\Variant;
|
use App\Domains\Commerce\Catalog\Models\Variant;
|
||||||
use App\Domains\Ticketing\Event\Models\EventDate;
|
|
||||||
use App\Domains\Core\Tenant\Models\Tenant;
|
use App\Domains\Core\Tenant\Models\Tenant;
|
||||||
|
use App\Domains\Ticketing\Event\Models\EventDate;
|
||||||
|
use App\Shared\Attachable\Models\Attachment;
|
||||||
|
use App\Shared\Attachable\Services\AttachmentService;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Pagination\LengthAwarePaginator;
|
use Illuminate\Pagination\LengthAwarePaginator;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
@@ -286,9 +286,11 @@ class CatalogService
|
|||||||
int $perPage,
|
int $perPage,
|
||||||
int $page,
|
int $page,
|
||||||
): LengthAwarePaginator {
|
): LengthAwarePaginator {
|
||||||
|
$categoryIds = $this->categoryAndDescendantIds($tenant, $category);
|
||||||
|
|
||||||
return CatalogItem::query()
|
return CatalogItem::query()
|
||||||
->forTenantCatalog($tenant)
|
->forTenantCatalog($tenant)
|
||||||
->where('category_id', $category->id)
|
->whereIn('category_id', $categoryIds)
|
||||||
->whereAvailable()
|
->whereAvailable()
|
||||||
->with([
|
->with([
|
||||||
'attachments',
|
'attachments',
|
||||||
@@ -306,6 +308,34 @@ class CatalogService
|
|||||||
->paginate(perPage: $perPage, pageName: 'page', page: $page);
|
->paginate(perPage: $perPage, pageName: 'page', page: $page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return array<int, int> */
|
||||||
|
private function categoryAndDescendantIds(Tenant $tenant, Category $category): array
|
||||||
|
{
|
||||||
|
$childrenByParent = Category::query()
|
||||||
|
->where('tenant_code', $tenant->codigo)
|
||||||
|
->get(['id', 'categoria_id'])
|
||||||
|
->groupBy('categoria_id');
|
||||||
|
|
||||||
|
$categoryIds = [];
|
||||||
|
$pendingIds = [$category->id];
|
||||||
|
|
||||||
|
while ($pendingIds !== []) {
|
||||||
|
$categoryId = array_shift($pendingIds);
|
||||||
|
|
||||||
|
if (isset($categoryIds[$categoryId])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$categoryIds[$categoryId] = $categoryId;
|
||||||
|
|
||||||
|
foreach ($childrenByParent->get($categoryId, collect()) as $child) {
|
||||||
|
$pendingIds[] = $child->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values($categoryIds);
|
||||||
|
}
|
||||||
|
|
||||||
public function delete(CatalogItem $catalogItem): void
|
public function delete(CatalogItem $catalogItem): void
|
||||||
{
|
{
|
||||||
DB::transaction(function () use ($catalogItem): void {
|
DB::transaction(function () use ($catalogItem): void {
|
||||||
|
|||||||
@@ -72,6 +72,29 @@ class CategoryDetailTest extends TestCase
|
|||||||
->assertJsonCount(0, 'data');
|
->assertJsonCount(0, 'data');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_it_includes_products_from_all_descendant_categories(): void
|
||||||
|
{
|
||||||
|
$tenant = $this->createTenant('category-descendants');
|
||||||
|
$parent = $this->createCategory($tenant, 'Indumentaria');
|
||||||
|
$child = $this->createCategory($tenant, 'Remeras', $parent);
|
||||||
|
$grandchild = $this->createCategory($tenant, 'Manga corta', $child);
|
||||||
|
$sibling = $this->createCategory($tenant, 'Pantalones');
|
||||||
|
|
||||||
|
$parentItem = $this->createCatalogItem($tenant, $parent, 'Producto padre');
|
||||||
|
$childItem = $this->createCatalogItem($tenant, $child, 'Producto hijo');
|
||||||
|
$grandchildItem = $this->createCatalogItem($tenant, $grandchild, 'Producto nieto');
|
||||||
|
$this->createCatalogItem($tenant, $sibling, 'Producto ajeno');
|
||||||
|
|
||||||
|
$this->getJson("/api/tenants/{$tenant->codigo}/categories/{$parent->id}")
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonPath('meta.total', 3)
|
||||||
|
->assertJsonCount(3, 'data')
|
||||||
|
->assertJsonPath('data.0.id', $childItem->id)
|
||||||
|
->assertJsonPath('data.1.id', $grandchildItem->id)
|
||||||
|
->assertJsonPath('data.2.id', $parentItem->id)
|
||||||
|
->assertJsonMissing(['nombre' => 'Producto ajeno']);
|
||||||
|
}
|
||||||
|
|
||||||
public function test_it_rejects_categories_from_another_tenant(): void
|
public function test_it_rejects_categories_from_another_tenant(): void
|
||||||
{
|
{
|
||||||
$tenant = $this->createTenant('category-owner');
|
$tenant = $this->createTenant('category-owner');
|
||||||
@@ -107,10 +130,14 @@ class CategoryDetailTest extends TestCase
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createCategory(Tenant $tenant, string $name): Category
|
private function createCategory(
|
||||||
{
|
Tenant $tenant,
|
||||||
|
string $name,
|
||||||
|
?Category $parent = null,
|
||||||
|
): Category {
|
||||||
return Category::query()->create([
|
return Category::query()->create([
|
||||||
'tenant_code' => $tenant->codigo,
|
'tenant_code' => $tenant->codigo,
|
||||||
|
'categoria_id' => $parent?->id,
|
||||||
'nombre' => $name,
|
'nombre' => $name,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user