Compare commits
30 Commits
87a4fa6288
...
843659583e
| Author | SHA1 | Date | |
|---|---|---|---|
| 843659583e | |||
| c2921166bd | |||
| 0f67e66b6b | |||
| 725abed06a | |||
| 743939fc63 | |||
| eb50e65fef | |||
| 1cf13501d8 | |||
| 2eeef8d392 | |||
| 07d2129410 | |||
| ceb1d1f1af | |||
| 885ab2a6e3 | |||
| 47196a572a | |||
| 45bba9516d | |||
| c6da71894e | |||
| 75d3c819d9 | |||
| bd6d672df2 | |||
| 4162e2c1cc | |||
| 8d01620fa8 | |||
| f8b8bab474 | |||
| 53d1415b18 | |||
| 6ff9119086 | |||
| 24e000b423 | |||
| c69ebe5f7c | |||
| c3a2da607d | |||
| d1c4acc7ae | |||
| f8c4cc7428 | |||
| 21cfbe2a46 | |||
| abc060ab25 | |||
| 80adbd9ca9 | |||
| 12c10bbe06 |
@@ -175,18 +175,30 @@ class CatalogItem extends Model
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @param Builder<CatalogItem> $query */
|
/** @param Builder<CatalogItem> $query */
|
||||||
public function scopeWhereVariantsAvailable(Builder $query): Builder
|
public function scopeWhereAvailable(Builder $query): Builder
|
||||||
{
|
{
|
||||||
return $query->where(function (Builder $query): void {
|
return $query->where(function (Builder $query): void {
|
||||||
$query
|
$query
|
||||||
->whereDoesntHave('variants')
|
->where('catalog_items.inventory_policy', InventoryPolicy::Unlimited->value)
|
||||||
->orWhere('catalog_items.inventory_policy', InventoryPolicy::Unlimited->value)
|
|
||||||
->orWhereHas(
|
->orWhereHas(
|
||||||
'variants.inventory',
|
'variants.inventory',
|
||||||
fn (Builder $inventoryQuery): Builder => $inventoryQuery
|
fn (Builder $inventoryQuery): Builder => $inventoryQuery
|
||||||
->whereColumn('inventories.real_stock', '>', 'inventories.reserved_stock')
|
->whereColumn('inventories.real_stock', '>', 'inventories.reserved_stock')
|
||||||
|
)
|
||||||
|
->orWhere(function (Builder $directItemQuery): void {
|
||||||
|
$directItemQuery
|
||||||
|
->whereDoesntHave('variants')
|
||||||
|
->where(function (Builder $inventoryQuery): void {
|
||||||
|
$inventoryQuery
|
||||||
|
->whereNull('catalog_items.inventory_id')
|
||||||
|
->orWhereHas(
|
||||||
|
'inventory',
|
||||||
|
fn (Builder $availableInventoryQuery): Builder => $availableInventoryQuery
|
||||||
|
->whereColumn('inventories.real_stock', '>', 'inventories.reserved_stock')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return Collection<int, Variant> */
|
/** @return Collection<int, Variant> */
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
|||||||
'tenant_code',
|
'tenant_code',
|
||||||
'categoria_id',
|
'categoria_id',
|
||||||
'nombre',
|
'nombre',
|
||||||
|
'is_enabled',
|
||||||
])]
|
])]
|
||||||
class Category extends Model
|
class Category extends Model
|
||||||
{
|
{
|
||||||
@@ -22,6 +23,10 @@ class Category extends Model
|
|||||||
|
|
||||||
protected $table = 'categorias';
|
protected $table = 'categorias';
|
||||||
|
|
||||||
|
protected $attributes = [
|
||||||
|
'is_enabled' => true,
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<string, string>
|
* @return array<string, string>
|
||||||
*/
|
*/
|
||||||
@@ -29,6 +34,7 @@ class Category extends Model
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'categoria_id' => 'integer',
|
'categoria_id' => 'integer',
|
||||||
|
'is_enabled' => 'boolean',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class CatalogFeaturedItemResource extends JsonResource
|
|||||||
$availableStock,
|
$availableStock,
|
||||||
$remainingUserQuota,
|
$remainingUserQuota,
|
||||||
),
|
),
|
||||||
'variants' => $catalogItem->variants
|
'variants' => $catalogItem->visibleVariants()
|
||||||
->map(function (Variant $variant) use ($catalogItem, $remainingUserQuota): array {
|
->map(function (Variant $variant) use ($catalogItem, $remainingUserQuota): array {
|
||||||
$variantStock = $catalogItem->inventory_policy === InventoryPolicy::Unlimited
|
$variantStock = $catalogItem->inventory_policy === InventoryPolicy::Unlimited
|
||||||
? null
|
? null
|
||||||
@@ -115,7 +115,7 @@ class CatalogFeaturedItemResource extends JsonResource
|
|||||||
private function firstImageUrl(CatalogItem $catalogItem): ?string
|
private function firstImageUrl(CatalogItem $catalogItem): ?string
|
||||||
{
|
{
|
||||||
$attachment = $catalogItem->attachments->first()
|
$attachment = $catalogItem->attachments->first()
|
||||||
?? $catalogItem->variants
|
?? $catalogItem->visibleVariants()
|
||||||
->flatMap(fn (Variant $variant) => $variant->attachments)
|
->flatMap(fn (Variant $variant) => $variant->attachments)
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ class CatalogSearchItemResource extends JsonResource
|
|||||||
public function toArray(Request $request): array
|
public function toArray(Request $request): array
|
||||||
{
|
{
|
||||||
$availableStock = $this->availableStock();
|
$availableStock = $this->availableStock();
|
||||||
|
$visibleVariants = $this->visibleVariants();
|
||||||
$attachment = $this->attachments->first()
|
$attachment = $this->attachments->first()
|
||||||
?? $this->variants
|
?? $visibleVariants
|
||||||
->flatMap(fn (Variant $variant) => $variant->attachments)
|
->flatMap(fn (Variant $variant) => $variant->attachments)
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
@@ -30,7 +31,7 @@ class CatalogSearchItemResource extends JsonResource
|
|||||||
'image' => $attachment?->getTemporaryUrl(1440),
|
'image' => $attachment?->getTemporaryUrl(1440),
|
||||||
'maximum_addable_quantity' => $this->maximumAddable($availableStock),
|
'maximum_addable_quantity' => $this->maximumAddable($availableStock),
|
||||||
'unavailable_message' => $this->unavailableMessage($availableStock),
|
'unavailable_message' => $this->unavailableMessage($availableStock),
|
||||||
'variants' => $this->variants
|
'variants' => $visibleVariants
|
||||||
->map(function (Variant $variant): array {
|
->map(function (Variant $variant): array {
|
||||||
$variantStock = $this->inventory_policy === InventoryPolicy::Unlimited
|
$variantStock = $this->inventory_policy === InventoryPolicy::Unlimited
|
||||||
? null
|
? null
|
||||||
|
|||||||
@@ -205,6 +205,13 @@ class CatalogService
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$visibleVariants = $catalogItem->visibleVariants();
|
$visibleVariants = $catalogItem->visibleVariants();
|
||||||
|
if ($catalogItem->type === CatalogItemType::Standard
|
||||||
|
&& ($catalogItem->inventory_id !== null || $catalogItem->variants->isNotEmpty())
|
||||||
|
&& ! $catalogItem->isAvailable()) {
|
||||||
|
throw new NotFoundHttpException('Catalog item is out of stock.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$catalogItem->setRelation('variants', $visibleVariants);
|
||||||
$selectedVariant = $variantId === null
|
$selectedVariant = $variantId === null
|
||||||
? $visibleVariants->first()
|
? $visibleVariants->first()
|
||||||
: $visibleVariants->firstWhere('id', $variantId);
|
: $visibleVariants->firstWhere('id', $variantId);
|
||||||
@@ -231,6 +238,7 @@ class CatalogService
|
|||||||
|
|
||||||
$paginator = CatalogItem::query()
|
$paginator = CatalogItem::query()
|
||||||
->where('tenant_code', $tenant->codigo)
|
->where('tenant_code', $tenant->codigo)
|
||||||
|
->whereAvailable()
|
||||||
->where(function (Builder $query) use ($containsPattern): void {
|
->where(function (Builder $query) use ($containsPattern): void {
|
||||||
$query
|
$query
|
||||||
->whereRaw('LOWER(nombre) LIKE ?', [$containsPattern])
|
->whereRaw('LOWER(nombre) LIKE ?', [$containsPattern])
|
||||||
@@ -280,6 +288,7 @@ class CatalogService
|
|||||||
return CatalogItem::query()
|
return CatalogItem::query()
|
||||||
->where('tenant_code', $tenant->codigo)
|
->where('tenant_code', $tenant->codigo)
|
||||||
->where('category_id', $category->id)
|
->where('category_id', $category->id)
|
||||||
|
->whereAvailable()
|
||||||
->with([
|
->with([
|
||||||
'attachments',
|
'attachments',
|
||||||
'inventory',
|
'inventory',
|
||||||
|
|||||||
@@ -49,6 +49,16 @@ class FeaturedGroupService
|
|||||||
{
|
{
|
||||||
$query = CatalogItem::query()
|
$query = CatalogItem::query()
|
||||||
->where('catalog_items.tenant_code', $featuredGroup->tenant_code)
|
->where('catalog_items.tenant_code', $featuredGroup->tenant_code)
|
||||||
|
->whereAvailable()
|
||||||
|
->where(function (Builder $query): void {
|
||||||
|
$query
|
||||||
|
->whereDoesntHave('category')
|
||||||
|
->orWhereHas(
|
||||||
|
'category',
|
||||||
|
fn (Builder $categoryQuery): Builder => $categoryQuery
|
||||||
|
->where('is_enabled', true),
|
||||||
|
);
|
||||||
|
})
|
||||||
->with([
|
->with([
|
||||||
'inventory',
|
'inventory',
|
||||||
'attachments',
|
'attachments',
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\FiestaFutbolInfantil\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\FiestaFutbolInfantil\Enums\FiestaCategory;
|
||||||
|
use App\Domains\FiestaFutbolInfantil\Requests\UpdateCategoryVisibilityRequest;
|
||||||
|
use App\Domains\FiestaFutbolInfantil\Services\CategoryVisibilityService;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class CategoryVisibilityController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(private readonly CategoryVisibilityService $visibilityService) {}
|
||||||
|
|
||||||
|
public function show(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$category = $this->category($request);
|
||||||
|
$tenant = $request->user()->tenant()->firstOrFail();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'data' => [
|
||||||
|
'is_enabled' => $this->visibilityService->isEnabled($tenant, $category),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(UpdateCategoryVisibilityRequest $request): JsonResponse
|
||||||
|
{
|
||||||
|
$category = $this->category($request);
|
||||||
|
$tenant = $request->user()->tenant()->firstOrFail();
|
||||||
|
$model = $this->visibilityService->update(
|
||||||
|
$tenant,
|
||||||
|
$category,
|
||||||
|
$request->boolean('is_enabled'),
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'data' => [
|
||||||
|
'is_enabled' => $model->is_enabled,
|
||||||
|
],
|
||||||
|
'message' => $model->is_enabled
|
||||||
|
? 'Mostrar en sitio web se activo correctamente'
|
||||||
|
: 'Mostrar en sitio web se desactivo correctamente',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function category(Request $request): FiestaCategory
|
||||||
|
{
|
||||||
|
return FiestaCategory::from((string) $request->route('category'));
|
||||||
|
}
|
||||||
|
}
|
||||||
21
app/Domains/FiestaFutbolInfantil/Enums/FiestaCategory.php
Normal file
21
app/Domains/FiestaFutbolInfantil/Enums/FiestaCategory.php
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\FiestaFutbolInfantil\Enums;
|
||||||
|
|
||||||
|
enum FiestaCategory: string
|
||||||
|
{
|
||||||
|
case Entries = 'entries';
|
||||||
|
case Foods = 'foods';
|
||||||
|
case Accommodations = 'accommodations';
|
||||||
|
case Merchandise = 'merchandise';
|
||||||
|
|
||||||
|
public function categoryName(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Entries => 'Entradas',
|
||||||
|
self::Foods => 'Comidas',
|
||||||
|
self::Accommodations => 'Alojamientos',
|
||||||
|
self::Merchandise => 'Merchandising',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\FiestaFutbolInfantil\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class UpdateCategoryVisibilityRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/** @return array<string, mixed> */
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'is_enabled' => ['required', 'boolean'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\FiestaFutbolInfantil\Services;
|
||||||
|
|
||||||
|
use App\Domains\Catalog\Models\Category;
|
||||||
|
use App\Domains\FiestaFutbolInfantil\Enums\FiestaCategory;
|
||||||
|
use App\Domains\Tenant\Models\Tenant;
|
||||||
|
|
||||||
|
class CategoryVisibilityService
|
||||||
|
{
|
||||||
|
public function isEnabled(Tenant $tenant, FiestaCategory $category): bool
|
||||||
|
{
|
||||||
|
return Category::query()
|
||||||
|
->where('tenant_code', $tenant->codigo)
|
||||||
|
->where('nombre', $category->categoryName())
|
||||||
|
->value('is_enabled') ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Tenant $tenant, FiestaCategory $category, bool $isEnabled): Category
|
||||||
|
{
|
||||||
|
$model = Category::query()->firstOrCreate([
|
||||||
|
'tenant_code' => $tenant->codigo,
|
||||||
|
'nombre' => $category->categoryName(),
|
||||||
|
]);
|
||||||
|
$model->update(['is_enabled' => $isEnabled]);
|
||||||
|
|
||||||
|
return $model;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Domains\FiestaFutbolInfantil\Controllers\AccommodationController;
|
use App\Domains\FiestaFutbolInfantil\Controllers\AccommodationController;
|
||||||
|
use App\Domains\FiestaFutbolInfantil\Controllers\CategoryVisibilityController;
|
||||||
use App\Domains\FiestaFutbolInfantil\Controllers\EntryController;
|
use App\Domains\FiestaFutbolInfantil\Controllers\EntryController;
|
||||||
use App\Domains\FiestaFutbolInfantil\Controllers\FoodController;
|
use App\Domains\FiestaFutbolInfantil\Controllers\FoodController;
|
||||||
use App\Domains\FiestaFutbolInfantil\Controllers\MerchandiseController;
|
use App\Domains\FiestaFutbolInfantil\Controllers\MerchandiseController;
|
||||||
@@ -9,6 +10,20 @@ use Illuminate\Support\Facades\Route;
|
|||||||
Route::prefix('v1/adminapp/tenant')
|
Route::prefix('v1/adminapp/tenant')
|
||||||
->middleware(['auth:sanctum', 'adminapp.tenant'])
|
->middleware(['auth:sanctum', 'adminapp.tenant'])
|
||||||
->group(function (): void {
|
->group(function (): void {
|
||||||
|
$visibilityRoutes = static function (string $endpoint, string $category, string $menuCode): void {
|
||||||
|
Route::get("{$endpoint}/visibility", [CategoryVisibilityController::class, 'show'])
|
||||||
|
->defaults('category', $category)
|
||||||
|
->middleware("tenant.menu:{$menuCode}");
|
||||||
|
Route::patch("{$endpoint}/visibility", [CategoryVisibilityController::class, 'update'])
|
||||||
|
->defaults('category', $category)
|
||||||
|
->middleware("tenant.menu:{$menuCode}");
|
||||||
|
};
|
||||||
|
|
||||||
|
$visibilityRoutes('entries', 'entries', 'adminapp.fiesta-futbol-infantil.entradas');
|
||||||
|
$visibilityRoutes('foods', 'foods', 'adminapp.fiesta-futbol-infantil.comida');
|
||||||
|
$visibilityRoutes('accommodations', 'accommodations', 'adminapp.fiesta-futbol-infantil.alojamientos');
|
||||||
|
$visibilityRoutes('merchandise', 'merchandise', 'adminapp.fiesta-futbol-infantil.merchandising');
|
||||||
|
|
||||||
Route::get('entries', [EntryController::class, 'index'])
|
Route::get('entries', [EntryController::class, 'index'])
|
||||||
->middleware('tenant.menu:adminapp.fiesta-futbol-infantil.entradas')
|
->middleware('tenant.menu:adminapp.fiesta-futbol-infantil.entradas')
|
||||||
->name('adminapp.fiesta-futbol-infantil.entries.index');
|
->name('adminapp.fiesta-futbol-infantil.entries.index');
|
||||||
|
|||||||
@@ -24,13 +24,22 @@ class PurchaseController extends Controller
|
|||||||
{
|
{
|
||||||
public function index(Request $request, Tenant $tenant): JsonResponse
|
public function index(Request $request, Tenant $tenant): JsonResponse
|
||||||
{
|
{
|
||||||
|
$statusParam = $request->query('status');
|
||||||
|
$statuses = is_string($statusParam)
|
||||||
|
? collect(explode(',', $statusParam))
|
||||||
|
->map(fn (string $status): string => trim($status))
|
||||||
|
->filter()
|
||||||
|
->unique()
|
||||||
|
->values()
|
||||||
|
->all()
|
||||||
|
: [];
|
||||||
|
|
||||||
return PurchaseResource::collection(
|
return PurchaseResource::collection(
|
||||||
Purchase::query()
|
Purchase::query()
|
||||||
->where('tenant_codigo', $tenant->codigo)
|
->where('tenant_codigo', $tenant->codigo)
|
||||||
->where('user_id', $request->user()->id)
|
->where('user_id', $request->user()->id)
|
||||||
->when($request->query('status'), function ($query, $status) {
|
->when($statuses !== [], fn ($query) => $query->whereIn('status', $statuses))
|
||||||
$query->where('status', $status);
|
->orderBy('status')
|
||||||
})
|
|
||||||
->latest()
|
->latest()
|
||||||
->paginateFromRequest()
|
->paginateFromRequest()
|
||||||
)->response();
|
)->response();
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('categorias', function (Blueprint $table): void {
|
||||||
|
$table->boolean('is_enabled')->default(true)->after('nombre');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('categorias', function (Blueprint $table): void {
|
||||||
|
$table->dropColumn('is_enabled');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -73,19 +73,18 @@ class CatalogControllerTest extends TestCase
|
|||||||
->assertJsonPath('0.items.0.descripcion', 'Variants description')
|
->assertJsonPath('0.items.0.descripcion', 'Variants description')
|
||||||
->assertJsonPath('0.items.0.precio', '100.00')
|
->assertJsonPath('0.items.0.precio', '100.00')
|
||||||
->assertJsonPath('0.items.0.maximum_addable_quantity', 7)
|
->assertJsonPath('0.items.0.maximum_addable_quantity', 7)
|
||||||
->assertJsonCount(3, '0.items.0.variants')
|
->assertJsonCount(2, '0.items.0.variants')
|
||||||
->assertJsonPath('0.items.0.variants.0.maximum_addable_quantity', 4)
|
->assertJsonPath('0.items.0.variants.0.maximum_addable_quantity', 4)
|
||||||
->assertJsonPath('0.items.0.variants.1.maximum_addable_quantity', 3)
|
->assertJsonPath('0.items.0.variants.1.maximum_addable_quantity', 3)
|
||||||
->assertJsonPath('0.items.0.variants.2.id', $unavailableVariant->id)
|
|
||||||
->assertJsonPath('0.items.0.variants.2.maximum_addable_quantity', 0)
|
|
||||||
->assertJsonPath(
|
|
||||||
'0.items.0.variants.2.unavailable_message',
|
|
||||||
'Este producto no tiene stock disponible.',
|
|
||||||
)
|
|
||||||
->assertJsonPath('1.title', 'Row')
|
->assertJsonPath('1.title', 'Row')
|
||||||
->assertJsonPath('1.items.data.0.maximum_addable_quantity', 8)
|
->assertJsonPath('1.items.data.0.maximum_addable_quantity', 8)
|
||||||
->assertJsonMissingPath('1.items.data.0.stock_tecnico')
|
->assertJsonMissingPath('1.items.data.0.stock_tecnico')
|
||||||
->assertJsonCount(0, '1.items.data.0.variants');
|
->assertJsonCount(0, '1.items.data.0.variants');
|
||||||
|
|
||||||
|
$this->assertNotContains(
|
||||||
|
$unavailableVariant->id,
|
||||||
|
collect($response->json('0.items.0.variants'))->pluck('id')->all(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_maximum_addable_quantity_shares_the_authenticated_user_quota_between_variants(): void
|
public function test_maximum_addable_quantity_shares_the_authenticated_user_quota_between_variants(): void
|
||||||
@@ -133,7 +132,7 @@ class CatalogControllerTest extends TestCase
|
|||||||
->assertJsonMissingPath('0.items.0.variants.1.stock_tecnico');
|
->assertJsonMissingPath('0.items.0.variants.1.stock_tecnico');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_it_includes_out_of_stock_items_with_an_unavailable_message(): void
|
public function test_it_excludes_out_of_stock_items(): void
|
||||||
{
|
{
|
||||||
$tenant = $this->createTenant('catalog-available-variants');
|
$tenant = $this->createTenant('catalog-available-variants');
|
||||||
$group = $this->createGroup(
|
$group = $this->createGroup(
|
||||||
@@ -161,15 +160,10 @@ class CatalogControllerTest extends TestCase
|
|||||||
|
|
||||||
$this->getJson("/api/tenants/{$tenant->codigo}/catalog")
|
$this->getJson("/api/tenants/{$tenant->codigo}/catalog")
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertJsonCount(2, '0.items')
|
->assertJsonCount(1, '0.items')
|
||||||
->assertJsonPath('0.items.0.nombre', 'Unavailable')
|
->assertJsonPath('0.items.0.nombre', 'Available')
|
||||||
->assertJsonPath('0.items.0.maximum_addable_quantity', 0)
|
->assertJsonPath('0.items.0.unavailable_message', null)
|
||||||
->assertJsonPath(
|
->assertJsonMissing(['nombre' => 'Unavailable']);
|
||||||
'0.items.0.unavailable_message',
|
|
||||||
'Este producto no tiene stock disponible.',
|
|
||||||
)
|
|
||||||
->assertJsonPath('0.items.1.nombre', 'Available')
|
|
||||||
->assertJsonPath('0.items.1.unavailable_message', null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_column_with_image_uses_item_image_then_variant_image_then_null(): void
|
public function test_column_with_image_uses_item_image_then_variant_image_then_null(): void
|
||||||
@@ -372,6 +366,53 @@ class CatalogControllerTest extends TestCase
|
|||||||
->assertJsonPath('1.items.meta.total', 2);
|
->assertJsonPath('1.items.meta.total', 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_index_excludes_items_from_disabled_categories(): void
|
||||||
|
{
|
||||||
|
$tenant = $this->createTenant('catalog-disabled-category');
|
||||||
|
$group = $this->createGroup(
|
||||||
|
$tenant,
|
||||||
|
ProductLayout::Row,
|
||||||
|
'All products',
|
||||||
|
groupLayout: GroupLayout::Simple,
|
||||||
|
);
|
||||||
|
$enabledCategory = Category::query()->create([
|
||||||
|
'tenant_code' => $tenant->codigo,
|
||||||
|
'nombre' => 'Enabled',
|
||||||
|
]);
|
||||||
|
$disabledCategory = Category::query()->create([
|
||||||
|
'tenant_code' => $tenant->codigo,
|
||||||
|
'nombre' => 'Disabled',
|
||||||
|
'is_enabled' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$enabledItem = $this->createItem($tenant, 'Enabled item');
|
||||||
|
$enabledItem->category()->associate($enabledCategory)->save();
|
||||||
|
$disabledItem = $this->createItem($tenant, 'Disabled item');
|
||||||
|
$disabledItem->category()->associate($disabledCategory)->save();
|
||||||
|
$uncategorizedItem = $this->createItem($tenant, 'Uncategorized item');
|
||||||
|
|
||||||
|
foreach ([$enabledItem, $disabledItem, $uncategorizedItem] as $order => $item) {
|
||||||
|
$group->featuredItems()->create([
|
||||||
|
'catalog_item_id' => $item->id,
|
||||||
|
'order' => $order,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->getJson("/api/tenants/{$tenant->codigo}/catalog")
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonCount(2, '0.items')
|
||||||
|
->assertJsonPath('0.items.0.nombre', 'Enabled item')
|
||||||
|
->assertJsonPath('0.items.1.nombre', 'Uncategorized item')
|
||||||
|
->assertJsonMissing(['nombre' => 'Disabled item']);
|
||||||
|
|
||||||
|
$this->getJson(
|
||||||
|
"/api/tenants/{$tenant->codigo}/catalog/featured-groups/{$group->id}/items"
|
||||||
|
)
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonCount(2)
|
||||||
|
->assertJsonMissing(['nombre' => 'Disabled item']);
|
||||||
|
}
|
||||||
|
|
||||||
private function createGroup(
|
private function createGroup(
|
||||||
Tenant $tenant,
|
Tenant $tenant,
|
||||||
ProductLayout $layout,
|
ProductLayout $layout,
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class CatalogItemDetailControllerTest extends TestCase
|
|||||||
$this->assertStringContainsString($itemImage->path, $response->json('data.images.0'));
|
$this->assertStringContainsString($itemImage->path, $response->json('data.images.0'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_it_lists_unavailable_variants_and_selects_the_first_available_one(): void
|
public function test_it_omits_unavailable_variants_and_selects_the_first_available_one(): void
|
||||||
{
|
{
|
||||||
Storage::fake('s3');
|
Storage::fake('s3');
|
||||||
$tenant = $this->createTenant('detail-default');
|
$tenant = $this->createTenant('detail-default');
|
||||||
@@ -69,14 +69,8 @@ class CatalogItemDetailControllerTest extends TestCase
|
|||||||
|
|
||||||
$response
|
$response
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertJsonCount(2, 'data.variants')
|
->assertJsonCount(1, 'data.variants')
|
||||||
->assertJsonPath('data.variants.0.id', $firstVariant->id)
|
->assertJsonPath('data.variants.0.id', $secondVariant->id)
|
||||||
->assertJsonPath('data.variants.0.maximum_addable_quantity', 0)
|
|
||||||
->assertJsonPath(
|
|
||||||
'data.variants.0.unavailable_message',
|
|
||||||
'Este producto no tiene stock disponible.',
|
|
||||||
)
|
|
||||||
->assertJsonPath('data.variants.1.id', $secondVariant->id)
|
|
||||||
->assertJsonPath('data.selected_variant.id', $secondVariant->id)
|
->assertJsonPath('data.selected_variant.id', $secondVariant->id)
|
||||||
->assertJsonPath('data.selected_variant.maximum_addable_quantity', 6)
|
->assertJsonPath('data.selected_variant.maximum_addable_quantity', 6)
|
||||||
->assertJsonMissingPath('data.selected_variant.stock_tecnico')
|
->assertJsonMissingPath('data.selected_variant.stock_tecnico')
|
||||||
@@ -84,6 +78,10 @@ class CatalogItemDetailControllerTest extends TestCase
|
|||||||
$response
|
$response
|
||||||
->assertJsonMissingPath('data.stock_tecnico')
|
->assertJsonMissingPath('data.stock_tecnico')
|
||||||
->assertJsonMissingPath('data.images');
|
->assertJsonMissingPath('data.images');
|
||||||
|
$this->assertNotContains(
|
||||||
|
$firstVariant->id,
|
||||||
|
collect($response->json('data.variants'))->pluck('id')->all(),
|
||||||
|
);
|
||||||
$this->assertStringContainsString($secondImage->path, $response->json('data.selected_variant.images.0'));
|
$this->assertStringContainsString($secondImage->path, $response->json('data.selected_variant.images.0'));
|
||||||
$this->assertStringNotContainsString($firstImage->path, $response->json('data.selected_variant.images.0'));
|
$this->assertStringNotContainsString($firstImage->path, $response->json('data.selected_variant.images.0'));
|
||||||
$this->assertStringNotContainsString($itemImage->path, $response->json('data.selected_variant.images.0'));
|
$this->assertStringNotContainsString($itemImage->path, $response->json('data.selected_variant.images.0'));
|
||||||
@@ -93,6 +91,19 @@ class CatalogItemDetailControllerTest extends TestCase
|
|||||||
)->assertNotFound();
|
)->assertNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_it_does_not_return_an_out_of_stock_item(): void
|
||||||
|
{
|
||||||
|
$tenant = $this->createTenant('detail-out-of-stock');
|
||||||
|
$inventory = Inventory::query()->create([
|
||||||
|
'real_stock' => 5,
|
||||||
|
'reserved_stock' => 5,
|
||||||
|
]);
|
||||||
|
$item = $this->createItem($tenant, 'Sold out item', $inventory);
|
||||||
|
|
||||||
|
$this->getJson("/api/tenants/{$tenant->codigo}/catalog-items/{$item->id}")
|
||||||
|
->assertNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
public function test_it_selects_the_requested_variant_and_lists_variant_values_and_stock(): void
|
public function test_it_selects_the_requested_variant_and_lists_variant_values_and_stock(): void
|
||||||
{
|
{
|
||||||
Storage::fake('s3');
|
Storage::fake('s3');
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace Tests\Feature\Catalog;
|
|||||||
use App\Domains\Attachable\Enums\AttachmentType;
|
use App\Domains\Attachable\Enums\AttachmentType;
|
||||||
use App\Domains\Attachable\Models\Attachment;
|
use App\Domains\Attachable\Models\Attachment;
|
||||||
use App\Domains\Catalog\Models\CatalogItem;
|
use App\Domains\Catalog\Models\CatalogItem;
|
||||||
|
use App\Domains\Catalog\Models\Category;
|
||||||
use App\Domains\Catalog\Models\Inventory;
|
use App\Domains\Catalog\Models\Inventory;
|
||||||
use App\Domains\Tenant\Models\Tenant;
|
use App\Domains\Tenant\Models\Tenant;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
@@ -15,6 +16,21 @@ class CatalogSchemaTest extends TestCase
|
|||||||
{
|
{
|
||||||
use RefreshDatabase;
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
public function test_categories_are_enabled_by_default(): void
|
||||||
|
{
|
||||||
|
$this->assertTrue(Schema::hasColumn('categorias', 'is_enabled'));
|
||||||
|
|
||||||
|
$category = Category::query()->create([
|
||||||
|
'nombre' => 'Enabled category',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertTrue($category->is_enabled);
|
||||||
|
$this->assertDatabaseHas('categorias', [
|
||||||
|
'id' => $category->id,
|
||||||
|
'is_enabled' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function test_legacy_product_tables_are_replaced_by_catalog_tables(): void
|
public function test_legacy_product_tables_are_replaced_by_catalog_tables(): void
|
||||||
{
|
{
|
||||||
$this->assertFalse(Schema::hasTable('productos'));
|
$this->assertFalse(Schema::hasTable('productos'));
|
||||||
|
|||||||
@@ -62,6 +62,24 @@ class CatalogSearchTest extends TestCase
|
|||||||
$this->createCatalogItem($tenant, "Running {$number}");
|
$this->createCatalogItem($tenant, "Running {$number}");
|
||||||
}
|
}
|
||||||
$exactMatch = $this->createCatalogItem($tenant, 'Running');
|
$exactMatch = $this->createCatalogItem($tenant, 'Running');
|
||||||
|
$outOfStock = CatalogItem::query()->create([
|
||||||
|
'tenant_code' => $tenant->codigo,
|
||||||
|
'slug' => 'running-sold-out',
|
||||||
|
'nombre' => 'Running sold out',
|
||||||
|
'descripcion' => 'Running sold out description',
|
||||||
|
'precio' => 100,
|
||||||
|
]);
|
||||||
|
$outOfStock->variants()->create([
|
||||||
|
'inventory_id' => Inventory::query()->create(['real_stock' => 0])->id,
|
||||||
|
]);
|
||||||
|
CatalogItem::query()->create([
|
||||||
|
'tenant_code' => $tenant->codigo,
|
||||||
|
'inventory_id' => Inventory::query()->create(['real_stock' => 2, 'reserved_stock' => 2])->id,
|
||||||
|
'slug' => 'running-direct-sold-out',
|
||||||
|
'nombre' => 'Running direct sold out',
|
||||||
|
'descripcion' => 'Running direct sold out description',
|
||||||
|
'precio' => 100,
|
||||||
|
]);
|
||||||
$this->createCatalogItem($tenant, 'Unrelated');
|
$this->createCatalogItem($tenant, 'Unrelated');
|
||||||
$this->createCatalogItem($otherTenant, 'Running foreign');
|
$this->createCatalogItem($otherTenant, 'Running foreign');
|
||||||
|
|
||||||
@@ -76,6 +94,8 @@ class CatalogSearchTest extends TestCase
|
|||||||
->assertJsonPath('meta.total', 6)
|
->assertJsonPath('meta.total', 6)
|
||||||
->assertJsonCount(4, 'data')
|
->assertJsonCount(4, 'data')
|
||||||
->assertJsonPath('data.0.id', $exactMatch->id)
|
->assertJsonPath('data.0.id', $exactMatch->id)
|
||||||
|
->assertJsonMissing(['nombre' => 'Running sold out'])
|
||||||
|
->assertJsonMissing(['nombre' => 'Running direct sold out'])
|
||||||
->assertJsonMissing(['nombre' => 'Running foreign'])
|
->assertJsonMissing(['nombre' => 'Running foreign'])
|
||||||
->assertJsonMissing(['nombre' => 'Unrelated']);
|
->assertJsonMissing(['nombre' => 'Unrelated']);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,17 @@ class CategoryDetailTest extends TestCase
|
|||||||
$this->createCatalogItem($tenant, $category, 'Remera C');
|
$this->createCatalogItem($tenant, $category, 'Remera C');
|
||||||
$firstItem = $this->createCatalogItem($tenant, $category, 'Remera A');
|
$firstItem = $this->createCatalogItem($tenant, $category, 'Remera A');
|
||||||
$secondItem = $this->createCatalogItem($tenant, $category, 'Remera B');
|
$secondItem = $this->createCatalogItem($tenant, $category, 'Remera B');
|
||||||
|
$outOfStock = CatalogItem::query()->create([
|
||||||
|
'tenant_code' => $tenant->codigo,
|
||||||
|
'category_id' => $category->id,
|
||||||
|
'slug' => 'remera-agotada',
|
||||||
|
'nombre' => 'Remera agotada',
|
||||||
|
'descripcion' => 'Sin stock',
|
||||||
|
'precio' => 100,
|
||||||
|
]);
|
||||||
|
$outOfStock->variants()->create([
|
||||||
|
'inventory_id' => Inventory::query()->create(['real_stock' => 0])->id,
|
||||||
|
]);
|
||||||
$this->createCatalogItem($tenant, $otherCategory, 'Pantalón');
|
$this->createCatalogItem($tenant, $otherCategory, 'Pantalón');
|
||||||
|
|
||||||
$this->getJson("/api/tenants/{$tenant->codigo}/categories/{$category->id}")
|
$this->getJson("/api/tenants/{$tenant->codigo}/categories/{$category->id}")
|
||||||
@@ -44,6 +55,7 @@ class CategoryDetailTest extends TestCase
|
|||||||
->assertJsonCount(2, 'data')
|
->assertJsonCount(2, 'data')
|
||||||
->assertJsonPath('data.0.id', $firstItem->id)
|
->assertJsonPath('data.0.id', $firstItem->id)
|
||||||
->assertJsonPath('data.1.id', $secondItem->id)
|
->assertJsonPath('data.1.id', $secondItem->id)
|
||||||
|
->assertJsonMissing(['nombre' => 'Remera agotada'])
|
||||||
->assertJsonMissing(['nombre' => 'Pantalón']);
|
->assertJsonMissing(['nombre' => 'Pantalón']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,59 @@ class EntryControllerTest extends TestCase
|
|||||||
->assertUnauthorized();
|
->assertUnauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_category_visibility_can_be_read_and_updated_for_every_fiesta_menu(): void
|
||||||
|
{
|
||||||
|
$tenant = $this->createFiestaTenant();
|
||||||
|
$categories = [
|
||||||
|
'entries' => ['Entradas', 'adminapp.fiesta-futbol-infantil.entradas'],
|
||||||
|
'foods' => ['Comidas', 'adminapp.fiesta-futbol-infantil.comida'],
|
||||||
|
'accommodations' => ['Alojamientos', 'adminapp.fiesta-futbol-infantil.alojamientos'],
|
||||||
|
'merchandise' => ['Merchandising', 'adminapp.fiesta-futbol-infantil.merchandising'],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($categories as [$label, $menuCode]) {
|
||||||
|
$menu = Menu::query()->firstOrCreate(
|
||||||
|
['code' => $menuCode],
|
||||||
|
['label' => $label, 'route' => '/admin'],
|
||||||
|
);
|
||||||
|
$tenant->menues()->syncWithoutDetaching([$menu->code]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Sanctum::actingAs($this->createAdminAppUser($tenant));
|
||||||
|
|
||||||
|
foreach ($categories as $endpoint => [$categoryName]) {
|
||||||
|
$this->getJson("/api/v1/adminapp/tenant/{$endpoint}/visibility")
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonPath('data.is_enabled', true);
|
||||||
|
|
||||||
|
$this->patchJson("/api/v1/adminapp/tenant/{$endpoint}/visibility", [
|
||||||
|
'is_enabled' => false,
|
||||||
|
])
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonPath('data.is_enabled', false)
|
||||||
|
->assertJsonPath(
|
||||||
|
'message',
|
||||||
|
'Mostrar en sitio web se desactivo correctamente',
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->patchJson("/api/v1/adminapp/tenant/{$endpoint}/visibility", [
|
||||||
|
'is_enabled' => true,
|
||||||
|
])
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonPath('data.is_enabled', true)
|
||||||
|
->assertJsonPath(
|
||||||
|
'message',
|
||||||
|
'Mostrar en sitio web se activo correctamente',
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('categorias', [
|
||||||
|
'tenant_code' => $tenant->codigo,
|
||||||
|
'nombre' => $categoryName,
|
||||||
|
'is_enabled' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function test_it_creates_multiple_entries_with_dates_and_tracked_inventory(): void
|
public function test_it_creates_multiple_entries_with_dates_and_tracked_inventory(): void
|
||||||
{
|
{
|
||||||
$tenant = $this->createFiestaTenant();
|
$tenant = $this->createFiestaTenant();
|
||||||
|
|||||||
@@ -1230,6 +1230,52 @@ class StorePurchaseTest extends TestCase
|
|||||||
->assertJsonPath('data.0.total', '100.00');
|
->assertJsonPath('data.0.total', '100.00');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_purchase_index_filters_multiple_comma_separated_statuses_and_orders_by_status_ascending_then_date(): void
|
||||||
|
{
|
||||||
|
$this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$olderPaid = Purchase::query()->create([
|
||||||
|
'tenant_codigo' => 'sonder',
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'status' => Purchase::STATUS_PAID,
|
||||||
|
'total' => '10.00',
|
||||||
|
]);
|
||||||
|
$newerInReview = Purchase::query()->create([
|
||||||
|
'tenant_codigo' => 'sonder',
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'status' => Purchase::STATUS_IN_REVIEW,
|
||||||
|
'total' => '20.00',
|
||||||
|
]);
|
||||||
|
$newerPaid = Purchase::query()->create([
|
||||||
|
'tenant_codigo' => 'sonder',
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'status' => Purchase::STATUS_PAID,
|
||||||
|
'total' => '30.00',
|
||||||
|
]);
|
||||||
|
Purchase::query()->create([
|
||||||
|
'tenant_codigo' => 'sonder',
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'status' => Purchase::STATUS_CANCELLED,
|
||||||
|
'total' => '40.00',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$olderPaid->forceFill(['created_at' => now()->subDays(3)])->saveQuietly();
|
||||||
|
$newerInReview->forceFill(['created_at' => now()])->saveQuietly();
|
||||||
|
$newerPaid->forceFill(['created_at' => now()->subDay()])->saveQuietly();
|
||||||
|
|
||||||
|
$this->actingAs($user, 'sanctum')
|
||||||
|
->getJson('/api/tenants/sonder/compras?status=paid,%20in_review,paid')
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonCount(3, 'data')
|
||||||
|
->assertJsonPath('data.0.id', $newerInReview->id)
|
||||||
|
->assertJsonPath('data.0.status', Purchase::STATUS_IN_REVIEW)
|
||||||
|
->assertJsonPath('data.1.id', $newerPaid->id)
|
||||||
|
->assertJsonPath('data.1.status', Purchase::STATUS_PAID)
|
||||||
|
->assertJsonPath('data.2.id', $olderPaid->id)
|
||||||
|
->assertJsonPath('data.2.status', Purchase::STATUS_PAID);
|
||||||
|
}
|
||||||
|
|
||||||
public function test_it_rejects_a_cart_from_another_user(): void
|
public function test_it_rejects_a_cart_from_another_user(): void
|
||||||
{
|
{
|
||||||
$this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
|
$this->createTenant('sonder', 'Sonder', 'sonder.com.ar');
|
||||||
|
|||||||
Reference in New Issue
Block a user