feat(scanner): add scanner category validation logic and related tests

This commit is contained in:
2026-08-13 11:26:13 -03:00
parent 329e0b1953
commit a2592987f5
12 changed files with 216 additions and 17 deletions

View File

@@ -15,12 +15,17 @@ class StoreStaffRequest extends FormRequest
/** @return array<string, mixed> */
public function rules(): array
{
$categoryRules = $this->user()->tenant()->firstOrFail()
->requiresScannerCategoryValidation()
? ['required', 'array', 'min:1']
: ['sometimes', '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')],
'category_ids' => $categoryRules,
'category_ids.*' => ['integer', 'distinct', Rule::exists('categorias', 'id')],
];
}
}

View File

@@ -16,6 +16,10 @@ class UpdateStaffRequest extends FormRequest
public function rules(): array
{
$staffId = (int) $this->route('staff');
$categoryRules = $this->user()->tenant()->firstOrFail()
->requiresScannerCategoryValidation()
? ['required', 'array', 'min:1']
: ['sometimes', 'array'];
return [
'nombre_apellido' => ['required', 'string', 'max:255'],
@@ -26,8 +30,8 @@ class UpdateStaffRequest extends FormRequest
'max:255',
Rule::unique('users', 'email')->ignore($staffId),
],
'category_ids' => ['required', 'array', 'min:1'],
'category_ids.*' => ['required', 'integer', 'distinct', Rule::exists('categorias', 'id')],
'category_ids' => $categoryRules,
'category_ids.*' => ['integer', 'distinct', Rule::exists('categorias', 'id')],
];
}
}

View File

@@ -48,9 +48,10 @@ class StaffService
/** @param array<string, mixed> $data */
public function create(Tenant $tenant, array $data): User
{
$this->assertCategoriesBelongToTenant($tenant, $data['category_ids']);
$categoryIds = $this->categoryIdsFor($tenant, $data);
$this->assertCategoriesBelongToTenant($tenant, $categoryIds);
return DB::transaction(function () use ($tenant, $data): User {
return DB::transaction(function () use ($tenant, $data, $categoryIds): User {
$staff = User::query()->create([
...Arr::only($data, ['nombre_apellido', 'dni', 'email']),
'email' => mb_strtolower(trim((string) $data['email'])),
@@ -58,7 +59,7 @@ class StaffService
'rol_codigo' => RoleCode::Scanner->value,
'tenant_codigo' => $tenant->codigo,
]);
$staff->scanCategories()->sync($data['category_ids']);
$staff->scanCategories()->sync($categoryIds);
return $staff->load('role', 'scanCategories');
});
@@ -68,13 +69,14 @@ class StaffService
public function update(Tenant $tenant, int $staffId, array $data): User
{
$staff = $this->find($tenant, $staffId);
$this->assertCategoriesBelongToTenant($tenant, $data['category_ids']);
$categoryIds = $this->categoryIdsFor($tenant, $data);
$this->assertCategoriesBelongToTenant($tenant, $categoryIds);
return DB::transaction(function () use ($staff, $data): User {
return DB::transaction(function () use ($staff, $data, $categoryIds): 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']);
$staff->scanCategories()->sync($categoryIds);
return $staff->load('role', 'scanCategories');
});
@@ -97,6 +99,19 @@ class StaffService
->where('rol_codigo', RoleCode::Scanner->value);
}
/**
* @param array<string, mixed> $data
* @return array<int, int>
*/
private function categoryIdsFor(Tenant $tenant, array $data): array
{
if (! $tenant->requiresScannerCategoryValidation()) {
return [];
}
return $data['category_ids'];
}
/** @param array<int, int> $categoryIds */
private function assertCategoriesBelongToTenant(Tenant $tenant, array $categoryIds): void
{

View File

@@ -38,6 +38,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
'display_categories',
'display_seach_bar',
'display_cart',
'scanner_category_validation_enabled',
'event_title',
'event_location',
'event_date_text',
@@ -53,6 +54,7 @@ class Tenant extends Model
'display_categories' => true,
'display_seach_bar' => true,
'display_cart' => true,
'scanner_category_validation_enabled' => true,
];
public function getRouteKeyName(): string
@@ -60,6 +62,11 @@ class Tenant extends Model
return 'codigo';
}
public function requiresScannerCategoryValidation(): bool
{
return $this->scanner_category_validation_enabled;
}
/**
* Get the attributes that should be cast.
*
@@ -74,6 +81,7 @@ class Tenant extends Model
'display_categories' => 'boolean',
'display_seach_bar' => 'boolean',
'display_cart' => 'boolean',
'scanner_category_validation_enabled' => 'boolean',
];
}

View File

@@ -80,6 +80,7 @@ class StoreTenantRequest extends FormRequest
'display_categories' => ['sometimes', 'boolean'],
'display_seach_bar' => ['sometimes', 'boolean'],
'display_cart' => ['sometimes', 'boolean'],
'scanner_category_validation_enabled' => ['sometimes', 'boolean'],
'website_type_code' => [
'required_with:extras',
'sometimes',

View File

@@ -90,6 +90,7 @@ class UpdateTenantRequest extends FormRequest
'display_categories' => ['sometimes', 'boolean'],
'display_seach_bar' => ['sometimes', 'boolean'],
'display_cart' => ['sometimes', 'boolean'],
'scanner_category_validation_enabled' => ['sometimes', 'boolean'],
];
}
}

View File

@@ -67,6 +67,7 @@ class TenantResource extends JsonResource
'display_categories' => $this->display_categories,
'display_seach_bar' => $this->display_seach_bar,
'display_cart' => $this->display_cart,
'scanner_category_validation_enabled' => $this->scanner_category_validation_enabled,
'social_media' => $this->whenLoaded(
'socialMedia',
fn () => $this->socialMedia

View File

@@ -69,12 +69,14 @@ class ScannerTicketService
public function detail(User $scanner, string $ticketUuid): Ticket
{
$categoryIds = $this->scannerCategoryIds($scanner);
return $this->baseQuery()
$query = $this->baseQuery()
->where('tenant_code', $scanner->tenant_codigo)
->where('ticket', $ticketUuid)
->where(function (Builder $query) use ($scanner, $categoryIds): void {
->where('ticket', $ticketUuid);
if ($scanner->tenant()->firstOrFail()->requiresScannerCategoryValidation()) {
$categoryIds = $this->scannerCategoryIds($scanner);
$query->where(function (Builder $query) use ($scanner, $categoryIds): void {
$query
->where('scanner_user_id', $scanner->getKey())
->orWhereHas(
@@ -82,8 +84,10 @@ class ScannerTicketService
fn (Builder $catalogItemQuery): Builder => $catalogItemQuery
->whereIn('category_id', $categoryIds)
);
})
->firstOrFail();
});
}
return $query->firstOrFail();
}
public function scan(User $scanner, string $ticketUuid): Ticket
@@ -153,6 +157,10 @@ class ScannerTicketService
private function scannerCanScan(User $scanner, Ticket $ticket): bool
{
if (! $scanner->tenant()->firstOrFail()->requiresScannerCategoryValidation()) {
return true;
}
$categoryId = $ticket->sourceCatalogItem?->category_id;
return $categoryId !== null