feat: Implement staff management features; add controller, service, resource, routes, and tests for staff and category assignments
This commit is contained in:
@@ -4,12 +4,14 @@ namespace App\Domains\Auth\Models;
|
||||
|
||||
use App\Domains\Authorization\Enums\RoleCode;
|
||||
use App\Domains\Authorization\Models\Role;
|
||||
use App\Domains\Catalog\Models\Category;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Database\Factories\UserFactory;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
@@ -59,6 +61,17 @@ class User extends Authenticatable
|
||||
return $this->belongsTo(Tenant::class, 'tenant_codigo', 'codigo');
|
||||
}
|
||||
|
||||
/** @return BelongsToMany<Category, $this> */
|
||||
public function scanCategories(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(
|
||||
Category::class,
|
||||
'category_scanners',
|
||||
'user_id',
|
||||
'categoria_id',
|
||||
)->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
|
||||
@@ -6,5 +6,6 @@ enum RoleCode: string
|
||||
{
|
||||
case Admin = 'admin';
|
||||
case AdminApp = 'adminapp';
|
||||
case Scanner = 'scanner';
|
||||
case User = 'user';
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
namespace App\Domains\Catalog\Models;
|
||||
|
||||
use App\Domains\Auth\Models\User;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
#[Fillable([
|
||||
@@ -64,4 +66,15 @@ class Category extends Model
|
||||
{
|
||||
return $this->hasMany(CatalogItem::class);
|
||||
}
|
||||
|
||||
/** @return BelongsToMany<User, $this> */
|
||||
public function scanners(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(
|
||||
User::class,
|
||||
'category_scanners',
|
||||
'categoria_id',
|
||||
'user_id',
|
||||
)->withTimestamps();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Forms\Controllers\AdminApp;
|
||||
|
||||
use App\Domains\Forms\Resources\StaffFormResource;
|
||||
use App\Domains\Forms\Services\StaffFormService;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class StaffFormController extends Controller
|
||||
{
|
||||
public function __construct(protected StaffFormService $staffFormService) {}
|
||||
|
||||
public function __invoke(Request $request): StaffFormResource
|
||||
{
|
||||
return StaffFormResource::make(
|
||||
$this->staffFormService->get(
|
||||
$request->user('sanctum')->tenant()->firstOrFail()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
20
app/Domains/Forms/Resources/StaffFormResource.php
Normal file
20
app/Domains/Forms/Resources/StaffFormResource.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Forms\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class StaffFormResource extends JsonResource
|
||||
{
|
||||
/** @return array<string, mixed> */
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'categories' => $this->resource['categories']->map(fn ($category) => [
|
||||
'id' => $category->id,
|
||||
'nombre' => $category->nombre,
|
||||
])->values(),
|
||||
];
|
||||
}
|
||||
}
|
||||
27
app/Domains/Forms/Services/StaffFormService.php
Normal file
27
app/Domains/Forms/Services/StaffFormService.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Forms\Services;
|
||||
|
||||
use App\Domains\Catalog\Models\Category;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class StaffFormService
|
||||
{
|
||||
/** @return array{categories: Collection<int, Category>} */
|
||||
public function get(Tenant $tenant): array
|
||||
{
|
||||
return [
|
||||
'categories' => Category::query()
|
||||
->whereNull('categoria_id')
|
||||
->where(function (Builder $query) use ($tenant): void {
|
||||
$query->where('tenant_code', $tenant->codigo)
|
||||
->orWhereHas('catalogItems', fn (Builder $items) => $items
|
||||
->where('tenant_code', $tenant->codigo));
|
||||
})
|
||||
->orderBy('nombre')
|
||||
->get(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use App\Domains\Forms\Controllers\AdminApp\EventFormController;
|
||||
use App\Domains\Forms\Controllers\AdminApp\SaleFormController;
|
||||
use App\Domains\Forms\Controllers\AdminApp\StaffFormController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::prefix('v1/adminapp/forms')
|
||||
@@ -9,4 +10,5 @@ Route::prefix('v1/adminapp/forms')
|
||||
->group(function (): void {
|
||||
Route::get('event', EventFormController::class);
|
||||
Route::get('sale', SaleFormController::class);
|
||||
Route::get('staff', StaffFormController::class);
|
||||
});
|
||||
|
||||
49
app/Domains/Staff/Controllers/AdminAppStaffController.php
Normal file
49
app/Domains/Staff/Controllers/AdminAppStaffController.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Staff\Controllers;
|
||||
|
||||
use App\Domains\Staff\Requests\StoreStaffRequest;
|
||||
use App\Domains\Staff\Requests\UpdateStaffRequest;
|
||||
use App\Domains\Staff\Resources\StaffResource;
|
||||
use App\Domains\Staff\Services\StaffService;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class AdminAppStaffController extends Controller
|
||||
{
|
||||
public function __construct(private readonly StaffService $staffService) {}
|
||||
|
||||
public function index(Request $request): AnonymousResourceCollection
|
||||
{
|
||||
return StaffResource::collection($this->staffService->list(
|
||||
$request->user()->tenant()->firstOrFail(),
|
||||
$request->string('search')->trim()->toString() ?: null,
|
||||
));
|
||||
}
|
||||
|
||||
public function store(StoreStaffRequest $request): StaffResource
|
||||
{
|
||||
return StaffResource::make($this->staffService->create(
|
||||
$request->user()->tenant()->firstOrFail(),
|
||||
$request->validated(),
|
||||
));
|
||||
}
|
||||
|
||||
public function update(UpdateStaffRequest $request, int $staff): StaffResource
|
||||
{
|
||||
return StaffResource::make($this->staffService->update(
|
||||
$request->user()->tenant()->firstOrFail(),
|
||||
$staff,
|
||||
$request->validated(),
|
||||
));
|
||||
}
|
||||
|
||||
public function destroy(Request $request, int $staff): Response
|
||||
{
|
||||
$this->staffService->delete($request->user()->tenant()->firstOrFail(), $staff);
|
||||
|
||||
return response()->noContent();
|
||||
}
|
||||
}
|
||||
26
app/Domains/Staff/Requests/StoreStaffRequest.php
Normal file
26
app/Domains/Staff/Requests/StoreStaffRequest.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Staff\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class StoreStaffRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'nombre_apellido' => ['required', 'string', 'max:255'],
|
||||
'dni' => ['required', 'string', 'max:50'],
|
||||
'email' => ['required', 'email', 'max:255', 'unique:users,email'],
|
||||
'category_ids' => ['required', 'array', 'min:1'],
|
||||
'category_ids.*' => ['required', 'integer', 'distinct', Rule::exists('categorias', 'id')],
|
||||
];
|
||||
}
|
||||
}
|
||||
33
app/Domains/Staff/Requests/UpdateStaffRequest.php
Normal file
33
app/Domains/Staff/Requests/UpdateStaffRequest.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Staff\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateStaffRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
$staffId = (int) $this->route('staff');
|
||||
|
||||
return [
|
||||
'nombre_apellido' => ['required', 'string', 'max:255'],
|
||||
'dni' => ['required', 'string', 'max:50'],
|
||||
'email' => [
|
||||
'required',
|
||||
'email',
|
||||
'max:255',
|
||||
Rule::unique('users', 'email')->ignore($staffId),
|
||||
],
|
||||
'category_ids' => ['required', 'array', 'min:1'],
|
||||
'category_ids.*' => ['required', 'integer', 'distinct', Rule::exists('categorias', 'id')],
|
||||
];
|
||||
}
|
||||
}
|
||||
32
app/Domains/Staff/Resources/StaffResource.php
Normal file
32
app/Domains/Staff/Resources/StaffResource.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Staff\Resources;
|
||||
|
||||
use App\Domains\Auth\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/** @mixin User */
|
||||
class StaffResource extends JsonResource
|
||||
{
|
||||
/** @return array<string, mixed> */
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'nombre_apellido' => $this->nombre_apellido,
|
||||
'dni' => $this->dni,
|
||||
'email' => $this->email,
|
||||
'rol_codigo' => $this->rol_codigo,
|
||||
'role' => $this->whenLoaded('role', fn () => [
|
||||
'codigo' => $this->role?->codigo,
|
||||
'nombre' => $this->role?->nombre,
|
||||
]),
|
||||
'categories' => $this->whenLoaded('scanCategories', fn () => $this->scanCategories
|
||||
->map(fn ($category) => [
|
||||
'id' => $category->id,
|
||||
'nombre' => $category->nombre,
|
||||
])->values()),
|
||||
];
|
||||
}
|
||||
}
|
||||
113
app/Domains/Staff/Services/StaffService.php
Normal file
113
app/Domains/Staff/Services/StaffService.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Staff\Services;
|
||||
|
||||
use App\Domains\Auth\Models\User;
|
||||
use App\Domains\Authorization\Enums\RoleCode;
|
||||
use App\Domains\Catalog\Models\Category;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class StaffService
|
||||
{
|
||||
/** @return Collection<int, User> */
|
||||
public function list(Tenant $tenant, ?string $search = null): Collection
|
||||
{
|
||||
return $this->staffQuery($tenant)
|
||||
->with(['role', 'scanCategories' => fn ($query) => $query->orderBy('nombre')])
|
||||
->when($search, function (Builder $query, string $search): void {
|
||||
$query->where(function (Builder $query) use ($search): void {
|
||||
$query->where('nombre_apellido', 'like', "%{$search}%")
|
||||
->orWhere('dni', 'like', "%{$search}%")
|
||||
->orWhere('email', 'like', "%{$search}%");
|
||||
});
|
||||
})
|
||||
->orderBy('nombre_apellido')
|
||||
->get();
|
||||
}
|
||||
|
||||
/** @return Collection<int, Category> */
|
||||
private function assignableCategories(Tenant $tenant): Collection
|
||||
{
|
||||
return Category::query()
|
||||
->whereNull('categoria_id')
|
||||
->where(function (Builder $query) use ($tenant): void {
|
||||
$query->where('tenant_code', $tenant->codigo)
|
||||
->orWhereHas('catalogItems', fn (Builder $items) => $items
|
||||
->where('tenant_code', $tenant->codigo));
|
||||
})
|
||||
->orderBy('nombre')
|
||||
->get();
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $data */
|
||||
public function create(Tenant $tenant, array $data): User
|
||||
{
|
||||
$this->assertCategoriesBelongToTenant($tenant, $data['category_ids']);
|
||||
|
||||
return DB::transaction(function () use ($tenant, $data): User {
|
||||
$staff = User::query()->create([
|
||||
...Arr::only($data, ['nombre_apellido', 'dni', 'email']),
|
||||
'email' => mb_strtolower(trim((string) $data['email'])),
|
||||
'password' => Str::random(64),
|
||||
'rol_codigo' => RoleCode::Scanner->value,
|
||||
'tenant_codigo' => $tenant->codigo,
|
||||
]);
|
||||
$staff->scanCategories()->sync($data['category_ids']);
|
||||
|
||||
return $staff->load('role', 'scanCategories');
|
||||
});
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $data */
|
||||
public function update(Tenant $tenant, int $staffId, array $data): User
|
||||
{
|
||||
$staff = $this->find($tenant, $staffId);
|
||||
$this->assertCategoriesBelongToTenant($tenant, $data['category_ids']);
|
||||
|
||||
return DB::transaction(function () use ($staff, $data): User {
|
||||
$attributes = Arr::only($data, ['nombre_apellido', 'dni', 'email']);
|
||||
$attributes['email'] = mb_strtolower(trim((string) $data['email']));
|
||||
$staff->update($attributes);
|
||||
$staff->scanCategories()->sync($data['category_ids']);
|
||||
|
||||
return $staff->load('role', 'scanCategories');
|
||||
});
|
||||
}
|
||||
|
||||
public function delete(Tenant $tenant, int $staffId): void
|
||||
{
|
||||
$this->find($tenant, $staffId)->delete();
|
||||
}
|
||||
|
||||
public function find(Tenant $tenant, int $staffId): User
|
||||
{
|
||||
return $this->staffQuery($tenant)->findOrFail($staffId);
|
||||
}
|
||||
|
||||
private function staffQuery(Tenant $tenant): Builder
|
||||
{
|
||||
return User::query()
|
||||
->where('tenant_codigo', $tenant->codigo)
|
||||
->where('rol_codigo', RoleCode::Scanner->value);
|
||||
}
|
||||
|
||||
/** @param array<int, int> $categoryIds */
|
||||
private function assertCategoriesBelongToTenant(Tenant $tenant, array $categoryIds): void
|
||||
{
|
||||
$validIds = $this->assignableCategories($tenant)
|
||||
->whereIn('id', $categoryIds)
|
||||
->pluck('id');
|
||||
|
||||
if ($validIds->count() !== count($categoryIds)) {
|
||||
throw ValidationException::withMessages([
|
||||
'category_ids' => 'Una o más categorías no pertenecen al tenant.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
10
app/Domains/Staff/routes/api.php
Normal file
10
app/Domains/Staff/routes/api.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Staff\Controllers\AdminAppStaffController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::prefix('v1/adminapp/tenant')
|
||||
->middleware(['auth:sanctum', 'adminapp.tenant'])
|
||||
->group(function (): void {
|
||||
Route::apiResource('staff', AdminAppStaffController::class)->except('show');
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
<?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::create('category_scanners', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnUpdate()->cascadeOnDelete();
|
||||
$table->foreignId('categoria_id')->constrained('categorias')->cascadeOnUpdate()->cascadeOnDelete();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['user_id', 'categoria_id']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('category_scanners');
|
||||
}
|
||||
};
|
||||
@@ -81,6 +81,10 @@ class AuthorizationSeeder extends Seeder
|
||||
'nombre' => 'Gestionar tickets',
|
||||
'descripcion' => 'Permite emitir, invalidar o regenerar tickets.',
|
||||
],
|
||||
'tickets.escanear' => [
|
||||
'nombre' => 'Escanear tickets',
|
||||
'descripcion' => 'Permite validar y consumir tickets de las categorías asignadas al usuario.',
|
||||
],
|
||||
'contenido.gestionar' => [
|
||||
'nombre' => 'Gestionar contenido',
|
||||
'descripcion' => 'Permite administrar menús, carruseles, destacados y redes sociales.',
|
||||
@@ -117,6 +121,11 @@ class AuthorizationSeeder extends Seeder
|
||||
'descripcion' => 'Accede a los menús administrativos de la aplicación.',
|
||||
'permisos' => [],
|
||||
],
|
||||
RoleCode::Scanner->value => [
|
||||
'nombre' => 'Scanner',
|
||||
'descripcion' => 'Valida y consume tickets de las categorías que tiene asignadas.',
|
||||
'permisos' => ['tickets.escanear'],
|
||||
],
|
||||
RoleCode::User->value => [
|
||||
'nombre' => 'Usuario',
|
||||
'descripcion' => 'Cliente final limitado a sus propios datos y operaciones.',
|
||||
|
||||
@@ -14,3 +14,4 @@ require __DIR__.'/../app/Domains/Ticket/routes/api.php';
|
||||
require __DIR__.'/../app/Domains/Event/routes/api.php';
|
||||
require __DIR__.'/../app/Domains/Bootstrap/routes/api.php';
|
||||
require __DIR__.'/../app/Domains/Forms/routes/api.php';
|
||||
require __DIR__.'/../app/Domains/Staff/routes/api.php';
|
||||
|
||||
84
tests/Feature/Forms/AdminAppStaffFormControllerTest.php
Normal file
84
tests/Feature/Forms/AdminAppStaffFormControllerTest.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Forms;
|
||||
|
||||
use App\Domains\Auth\Models\User;
|
||||
use App\Domains\Authorization\Enums\RoleCode;
|
||||
use App\Domains\Catalog\Models\Category;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use App\Domains\Tenant\Models\WebsiteType;
|
||||
use Database\Seeders\AuthorizationSeeder;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AdminAppStaffFormControllerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->seed(AuthorizationSeeder::class);
|
||||
WebsiteType::query()->create([
|
||||
'codigo' => 'onticket',
|
||||
'nombre' => 'OnTicket',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_authentication_is_required(): void
|
||||
{
|
||||
$this->getJson('/api/v1/adminapp/forms/staff')->assertUnauthorized();
|
||||
}
|
||||
|
||||
public function test_adminapp_user_gets_only_its_tenant_categories(): void
|
||||
{
|
||||
$tenant = $this->createTenant('acme');
|
||||
$otherTenant = $this->createTenant('other');
|
||||
$category = Category::query()->create([
|
||||
'tenant_code' => $tenant->codigo,
|
||||
'nombre' => 'Bebidas',
|
||||
]);
|
||||
Category::query()->create([
|
||||
'tenant_code' => $tenant->codigo,
|
||||
'categoria_id' => $category->id,
|
||||
'nombre' => 'Gaseosas',
|
||||
]);
|
||||
Category::query()->create([
|
||||
'tenant_code' => $otherTenant->codigo,
|
||||
'nombre' => 'Privada',
|
||||
]);
|
||||
Sanctum::actingAs(User::factory()->create([
|
||||
'rol_codigo' => RoleCode::AdminApp->value,
|
||||
'tenant_codigo' => $tenant->codigo,
|
||||
]));
|
||||
|
||||
$this->getJson('/api/v1/adminapp/forms/staff')
|
||||
->assertOk()
|
||||
->assertJsonCount(1, 'data.categories')
|
||||
->assertJsonPath('data.categories.0.id', $category->id)
|
||||
->assertJsonPath('data.categories.0.nombre', 'Bebidas')
|
||||
->assertJsonMissingPath('data.categories.0.categoria_id')
|
||||
->assertJsonMissingPath('data.roles');
|
||||
}
|
||||
|
||||
public function test_customer_cannot_get_staff_form(): void
|
||||
{
|
||||
Sanctum::actingAs(User::factory()->create([
|
||||
'rol_codigo' => RoleCode::User->value,
|
||||
]));
|
||||
|
||||
$this->getJson('/api/v1/adminapp/forms/staff')->assertForbidden();
|
||||
}
|
||||
|
||||
private function createTenant(string $code): Tenant
|
||||
{
|
||||
return Tenant::query()->create([
|
||||
'codigo' => $code,
|
||||
'nombre' => ucfirst($code),
|
||||
'dominio' => "{$code}.test",
|
||||
'website_type_code' => 'onticket',
|
||||
]);
|
||||
}
|
||||
}
|
||||
138
tests/Feature/Staff/StaffControllerTest.php
Normal file
138
tests/Feature/Staff/StaffControllerTest.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Staff;
|
||||
|
||||
use App\Domains\Auth\Models\User;
|
||||
use App\Domains\Authorization\Enums\RoleCode;
|
||||
use App\Domains\Catalog\Models\Category;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use App\Domains\Tenant\Models\WebsiteType;
|
||||
use Database\Seeders\AuthorizationSeeder;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
use Tests\TestCase;
|
||||
|
||||
class StaffControllerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private Tenant $tenant;
|
||||
|
||||
private User $admin;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->seed(AuthorizationSeeder::class);
|
||||
WebsiteType::query()->create(['codigo' => 'onticket', 'nombre' => 'OnTicket']);
|
||||
$this->tenant = Tenant::query()->create([
|
||||
'codigo' => 'acme',
|
||||
'nombre' => 'Acme',
|
||||
'dominio' => 'acme.test',
|
||||
'website_type_code' => 'onticket',
|
||||
]);
|
||||
$this->admin = User::factory()->create([
|
||||
'rol_codigo' => RoleCode::AdminApp->value,
|
||||
'tenant_codigo' => $this->tenant->codigo,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_adminapp_can_create_update_list_and_delete_staff_with_categories(): void
|
||||
{
|
||||
Sanctum::actingAs($this->admin);
|
||||
$firstCategory = $this->createCategory('Bebidas');
|
||||
$secondCategory = $this->createCategory('Comidas');
|
||||
|
||||
$response = $this->postJson('/api/v1/adminapp/tenant/staff', [
|
||||
'nombre_apellido' => 'Ada Lovelace',
|
||||
'dni' => '12345678',
|
||||
'email' => 'ADA@example.test',
|
||||
'category_ids' => [$firstCategory->id],
|
||||
])->assertSuccessful()
|
||||
->assertJsonPath('data.email', 'ada@example.test')
|
||||
->assertJsonPath('data.role.codigo', RoleCode::Scanner->value)
|
||||
->assertJsonPath('data.categories.0.id', $firstCategory->id);
|
||||
|
||||
$staffId = $response->json('data.id');
|
||||
$this->assertDatabaseHas('category_scanners', [
|
||||
'user_id' => $staffId,
|
||||
'categoria_id' => $firstCategory->id,
|
||||
]);
|
||||
|
||||
$this->getJson('/api/v1/adminapp/tenant/staff?search=ada')
|
||||
->assertOk()
|
||||
->assertJsonCount(1, 'data');
|
||||
|
||||
$this->putJson("/api/v1/adminapp/tenant/staff/{$staffId}", [
|
||||
'nombre_apellido' => 'Ada Byron',
|
||||
'dni' => '12345678',
|
||||
'email' => 'ada@example.test',
|
||||
'category_ids' => [$secondCategory->id],
|
||||
])->assertOk()
|
||||
->assertJsonPath('data.nombre_apellido', 'Ada Byron')
|
||||
->assertJsonPath('data.categories.0.id', $secondCategory->id);
|
||||
|
||||
$this->assertDatabaseMissing('category_scanners', [
|
||||
'user_id' => $staffId,
|
||||
'categoria_id' => $firstCategory->id,
|
||||
]);
|
||||
|
||||
$this->deleteJson("/api/v1/adminapp/tenant/staff/{$staffId}")->assertNoContent();
|
||||
$this->assertDatabaseMissing('users', ['id' => $staffId]);
|
||||
}
|
||||
|
||||
public function test_admin_cannot_assign_another_tenants_category(): void
|
||||
{
|
||||
Sanctum::actingAs($this->admin);
|
||||
$otherTenant = Tenant::query()->create([
|
||||
'codigo' => 'other',
|
||||
'nombre' => 'Other',
|
||||
'dominio' => 'other.test',
|
||||
'website_type_code' => 'onticket',
|
||||
]);
|
||||
$foreignCategory = Category::query()->create([
|
||||
'tenant_code' => $otherTenant->codigo,
|
||||
'nombre' => 'Privada',
|
||||
]);
|
||||
$payload = [
|
||||
'nombre_apellido' => 'Grace Hopper',
|
||||
'dni' => '87654321',
|
||||
'email' => 'grace@example.test',
|
||||
'category_ids' => [$foreignCategory->id],
|
||||
];
|
||||
|
||||
$this->postJson('/api/v1/adminapp/tenant/staff', $payload)
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors('category_ids');
|
||||
|
||||
$parent = $this->createCategory('Local');
|
||||
$child = Category::query()->create([
|
||||
'tenant_code' => $this->tenant->codigo,
|
||||
'categoria_id' => $parent->id,
|
||||
'nombre' => 'Subcategoría',
|
||||
]);
|
||||
$payload['category_ids'] = [$child->id];
|
||||
|
||||
$this->postJson('/api/v1/adminapp/tenant/staff', $payload)
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors('category_ids');
|
||||
}
|
||||
|
||||
public function test_customer_cannot_manage_staff(): void
|
||||
{
|
||||
Sanctum::actingAs(User::factory()->create([
|
||||
'rol_codigo' => RoleCode::User->value,
|
||||
]));
|
||||
|
||||
$this->getJson('/api/v1/adminapp/tenant/staff')->assertForbidden();
|
||||
}
|
||||
|
||||
private function createCategory(string $name): Category
|
||||
{
|
||||
return Category::query()->create([
|
||||
'tenant_code' => $this->tenant->codigo,
|
||||
'nombre' => $name,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user