Compare commits

..

17 Commits

Author SHA1 Message Date
6d123cd403 docs(postman): regenerate collection after API cleanup 2026-08-27 12:11:08 -03:00
3164a00e6c docs(menu): describe internal-only configuration 2026-08-27 12:11:08 -03:00
36cbc8da29 docs(integration): remove deleted endpoint references 2026-08-27 12:11:08 -03:00
58e027cad5 docs(catalog): remove featured group admin references 2026-08-27 12:11:08 -03:00
971c5f6cd9 refactor(checkout): remove unused completion endpoint 2026-08-27 12:11:08 -03:00
2524f00dfd refactor(staff): restrict updates to PUT 2026-08-27 12:10:57 -03:00
045fcfab72 refactor(desfile): remove unused image deletion endpoint 2026-08-27 12:10:57 -03:00
cfe29b41a9 refactor(tenant): remove single website extra endpoint 2026-08-27 12:10:56 -03:00
bd600649b6 refactor(catalog): remove unused featured group admin API 2026-08-27 12:10:56 -03:00
7685089540 refactor(storage-test): remove unused S3 test endpoints 2026-08-27 12:10:41 -03:00
889e188deb refactor(mail-test): remove unused developer endpoint 2026-08-27 12:10:41 -03:00
4a166e3cbf refactor(telepagos): remove unused webhook endpoint 2026-08-27 12:10:33 -03:00
d8b3a354d9 refactor(integration): remove unused global CRUD API 2026-08-27 12:10:33 -03:00
44f75185a1 refactor(menu): remove unused management API 2026-08-27 12:10:33 -03:00
3adef9341e refactor(tenant): remove unused CRUD API layer 2026-08-27 12:10:23 -03:00
3b1698ffd1 refactor(client): remove unused CRUD API layer 2026-08-27 12:10:23 -03:00
c436302d7a refactor(routes): unregister unused backend APIs 2026-08-27 12:10:15 -03:00
726 changed files with 4144 additions and 29159 deletions

View File

@@ -5,10 +5,6 @@ APP_DEBUG=false
APP_URL=http://localhost APP_URL=http://localhost
PURCHASE_CHECKOUT_EXPIRATION_MINUTES=30 PURCHASE_CHECKOUT_EXPIRATION_MINUTES=30
PURCHASE_QR_EXPIRATION_MINUTES=15
PURCHASE_TELEPAGOS_EXPIRATION_MINUTES=30
PURCHASE_TRANSFER_EXPIRATION_MINUTES=1440
PURCHASE_TRANSFER_CANDIDATE_AMOUNT_TOLERANCE_PERCENTAGE=5
STOCK_RESERVATION_EXPIRATION_MINUTES=30 STOCK_RESERVATION_EXPIRATION_MINUTES=30
FRONTEND_URLS=http://localhost:4200 FRONTEND_URLS=http://localhost:4200
@@ -45,7 +41,6 @@ COMMANDS_LOG_LEVEL=info
COMMANDS_LOG_DAYS=30 COMMANDS_LOG_DAYS=30
EMAILS_LOG_LEVEL=info EMAILS_LOG_LEVEL=info
EMAILS_LOG_DAYS=30 EMAILS_LOG_DAYS=30
EMAIL_DELIVERY_LEASE_SECONDS=300
DB_CONNECTION=mysql DB_CONNECTION=mysql
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1

View File

@@ -1,29 +1,19 @@
# Project Conventions # Project Conventions
## Test database safety
- Tests must use SQLite `:memory:` through `tests/bootstrap.php` and `Tests\TestCase`.
- Never run tests, `migrate:fresh`, `migrate:refresh`, or `db:wipe` against a persistent database, including the developer's `shopit` database.
- Never bypass the connection safety guard to resolve test failures. Use `php tests/verify-database-safety.php` to verify isolation without queries or migrations.
## Architecture ## Architecture
This project uses a domain-oriented structure grouped by business area under This project uses a domain-oriented structure under `app/Domains`.
`app/Domains`. Cross-cutting modules live under `app/Shared`.
Business domains must live in: Each domain must live in:
```text ```text
app/Domains/<Area>/<DomainName>/ app/Domains/<DomainName>/
``` ```
The current areas are `Core`, `Commerce`, and `Ticketing`. Cross-cutting
modules live in `app/Shared/<ModuleName>/`.
Supported folders inside each domain: Supported folders inside each domain:
```text ```text
app/Domains/<Area>/<DomainName>/ app/Domains/<DomainName>/
├── Models/ ├── Models/
├── Controllers/ ├── Controllers/
├── Requests/ ├── Requests/
@@ -38,28 +28,22 @@ app/Domains/<Area>/<DomainName>/
- Root API routes live in `routes/api.php`. - Root API routes live in `routes/api.php`.
- `routes/api.php` should act as an aggregator only. - `routes/api.php` should act as an aggregator only.
- Each business domain defines its own routes in - Each domain defines its own routes in `app/Domains/<DomainName>/routes/api.php`.
`app/Domains/<Area>/<DomainName>/routes/api.php`.
- Shared modules that expose routes define them in
`app/Shared/<ModuleName>/routes/api.php`.
## Namespaces ## Namespaces
Use this namespace pattern: Use this namespace pattern:
```php ```php
App\Domains\<Area>\<DomainName>\Models App\Domains\<DomainName>\Models
App\Domains\<Area>\<DomainName>\Controllers App\Domains\<DomainName>\Controllers
App\Domains\<Area>\<DomainName>\Requests App\Domains\<DomainName>\Requests
App\Domains\<Area>\<DomainName>\Resources App\Domains\<DomainName>\Resources
App\Domains\<Area>\<DomainName>\Services App\Domains\<DomainName>\Services
App\Domains\<Area>\<DomainName>\Policies App\Domains\<DomainName>\Policies
App\Domains\<Area>\<DomainName>\Exceptions App\Domains\<DomainName>\Exceptions
``` ```
For cross-cutting modules, use `App\Shared\<ModuleName>\...`. Reusable enums
and rules live directly under `App\Shared`.
## Responsibilities ## Responsibilities
- `Models/`: Eloquent models and model relationships. - `Models/`: Eloquent models and model relationships.
@@ -90,19 +74,13 @@ and rules live directly under `App\Shared`.
- Namespace, folder, and class names must stay aligned. - Namespace, folder, and class names must stay aligned.
- Route files should be named `api.php`. - Route files should be named `api.php`.
## Current Areas and Modules ## Current Domains
- `Core`: `Administrator`, `Auth`, `Authorization`, `Bootstrap`, `Client`, - `Tenant`
`Menu`, `Staff`, `Tenant`. - `Product`
- `Commerce`: `Cart`, `Catalog`, `Purchase`, `Sale`.
- `Ticketing`: `Desfile`, `Event`, `FiestaFutbolInfantil`, `Ticket`.
- `Shared`: `Attachable`, `Forms`, `Integration`, `Logging`, `MailTest`,
`Notification`, `StorageTest`, plus shared enums and rules.
## Notes For Future Changes ## Notes For Future Changes
- New features should be added inside the corresponding domain first. - New features should be added inside the corresponding domain first.
- Add new business features to the appropriate area under `app/Domains`. - If a concern does not belong to an existing domain, create a new domain under `app/Domains`.
- Keep generic reusable code in `app/Shared`; do not use it as a catch-all for
business features.
- Avoid placing new business code in `app/Http/Controllers` or `app/Models`. - Avoid placing new business code in `app/Http/Controllers` or `app/Models`.

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Shared\Attachable\Enums; namespace App\Domains\Attachable\Enums;
enum AttachmentType: string enum AttachmentType: string
{ {

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Shared\Attachable\Exceptions; namespace App\Domains\Attachable\Exceptions;
use RuntimeException; use RuntimeException;

View File

@@ -1,8 +1,8 @@
<?php <?php
namespace App\Shared\Attachable\Models; namespace App\Domains\Attachable\Models;
use App\Shared\Attachable\Enums\AttachmentType; use App\Domains\Attachable\Enums\AttachmentType;
use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Shared\Attachable\Models; namespace App\Domains\Attachable\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;

View File

@@ -1,11 +1,11 @@
<?php <?php
namespace App\Shared\Attachable\Services; namespace App\Domains\Attachable\Services;
use App\Shared\Attachable\Enums\AttachmentType; use App\Domains\Attachable\Enums\AttachmentType;
use App\Shared\Attachable\Exceptions\AttachmentStorageException; use App\Domains\Attachable\Exceptions\AttachmentStorageException;
use App\Shared\Attachable\Models\Attachment; use App\Domains\Attachable\Models\Attachment;
use App\Shared\Attachable\Models\AttachmentCrop; use App\Domains\Attachable\Models\AttachmentCrop;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;

View File

@@ -1,10 +1,10 @@
<?php <?php
namespace App\Domains\Core\Auth\Controllers; namespace App\Domains\Auth\Controllers;
use App\Domains\Core\Auth\Requests\AdminAppLoginRequest; use App\Domains\Auth\Requests\AdminAppLoginRequest;
use App\Domains\Core\Auth\Resources\UserResource; use App\Domains\Auth\Resources\UserResource;
use App\Domains\Core\Auth\Services\PasswordLoginService; use App\Domains\Auth\Services\PasswordLoginService;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;

View File

@@ -1,10 +1,10 @@
<?php <?php
namespace App\Domains\Core\Auth\Controllers; namespace App\Domains\Auth\Controllers;
use App\Domains\Core\Auth\Models\User; use App\Domains\Auth\Models\User;
use App\Domains\Core\Auth\Resources\AdminAppMeResource; use App\Domains\Auth\Resources\AdminAppMeResource;
use App\Domains\Core\Auth\Services\AdminAppContextService; use App\Domains\Auth\Services\AdminAppContextService;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\Request; use Illuminate\Http\Request;

View File

@@ -1,10 +1,10 @@
<?php <?php
namespace App\Domains\Core\Auth\Controllers; namespace App\Domains\Auth\Controllers;
use App\Domains\Core\Auth\Models\ResetPasswordAttempt; use App\Domains\Auth\Models\ResetPasswordAttempt;
use App\Domains\Core\Auth\Requests\AdminAppCreateResetPasswordAttemptRequest; use App\Domains\Auth\Requests\AdminAppCreateResetPasswordAttemptRequest;
use App\Domains\Core\Auth\Services\ResetPasswordAttemptService; use App\Domains\Auth\Services\ResetPasswordAttemptService;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;

View File

@@ -1,10 +1,10 @@
<?php <?php
namespace App\Domains\Core\Auth\Controllers; namespace App\Domains\Auth\Controllers;
use App\Domains\Core\Auth\Models\ResetPasswordAttempt; use App\Domains\Auth\Models\ResetPasswordAttempt;
use App\Domains\Core\Auth\Requests\CreateResetPasswordAttemptRequest; use App\Domains\Auth\Requests\CreateResetPasswordAttemptRequest;
use App\Domains\Core\Auth\Services\ResetPasswordAttemptService; use App\Domains\Auth\Services\ResetPasswordAttemptService;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;

View File

@@ -1,10 +1,10 @@
<?php <?php
namespace App\Domains\Core\Auth\Controllers; namespace App\Domains\Auth\Controllers;
use App\Domains\Core\Auth\Models\ResetPasswordAttempt; use App\Domains\Auth\Models\ResetPasswordAttempt;
use App\Domains\Core\Auth\Requests\ScannerCreateResetPasswordAttemptRequest; use App\Domains\Auth\Requests\ScannerCreateResetPasswordAttemptRequest;
use App\Domains\Core\Auth\Services\ResetPasswordAttemptService; use App\Domains\Auth\Services\ResetPasswordAttemptService;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;

View File

@@ -1,8 +1,8 @@
<?php <?php
namespace App\Domains\Core\Auth\Controllers; namespace App\Domains\Auth\Controllers;
use App\Domains\Core\Auth\Services\GoogleAuthService; use App\Domains\Auth\Services\GoogleAuthService;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;

View File

@@ -1,11 +1,11 @@
<?php <?php
namespace App\Domains\Core\Auth\Controllers; namespace App\Domains\Auth\Controllers;
use App\Domains\Core\Auth\Requests\GoogleTokenExchangeRequest; use App\Domains\Auth\Requests\GoogleTokenExchangeRequest;
use App\Domains\Core\Auth\Resources\UserResource; use App\Domains\Auth\Resources\UserResource;
use App\Domains\Core\Auth\Services\GoogleAuthService; use App\Domains\Auth\Services\GoogleAuthService;
use App\Domains\Commerce\Cart\Services\GuestCartMergeService; use App\Domains\Cart\Services\GuestCartMergeService;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Cookie; use Illuminate\Support\Facades\Cookie;

View File

@@ -1,11 +1,11 @@
<?php <?php
namespace App\Domains\Core\Auth\Controllers; namespace App\Domains\Auth\Controllers;
use App\Domains\Core\Auth\Requests\LoginUserRequest; use App\Domains\Auth\Requests\LoginUserRequest;
use App\Domains\Core\Auth\Resources\UserResource; use App\Domains\Auth\Resources\UserResource;
use App\Domains\Core\Auth\Services\PasswordLoginService; use App\Domains\Auth\Services\PasswordLoginService;
use App\Domains\Commerce\Cart\Services\GuestCartMergeService; use App\Domains\Cart\Services\GuestCartMergeService;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Cookie; use Illuminate\Support\Facades\Cookie;

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Core\Auth\Controllers; namespace App\Domains\Auth\Controllers;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;

View File

@@ -1,8 +1,8 @@
<?php <?php
namespace App\Domains\Core\Auth\Controllers; namespace App\Domains\Auth\Controllers;
use App\Domains\Core\Auth\Resources\UserResource; use App\Domains\Auth\Resources\UserResource;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;

View File

@@ -1,10 +1,10 @@
<?php <?php
namespace App\Domains\Core\Auth\Controllers; namespace App\Domains\Auth\Controllers;
use App\Domains\Core\Auth\Requests\RegisterUserRequest; use App\Domains\Auth\Requests\RegisterUserRequest;
use App\Domains\Core\Auth\Resources\UserResource; use App\Domains\Auth\Resources\UserResource;
use App\Domains\Core\Auth\Services\RegisterUserService; use App\Domains\Auth\Services\RegisterUserService;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;

View File

@@ -1,11 +1,10 @@
<?php <?php
namespace App\Domains\Core\Auth\Controllers; namespace App\Domains\Auth\Controllers;
use App\Domains\Core\Auth\Models\ResetPasswordAttempt; use App\Domains\Auth\Models\ResetPasswordAttempt;
use App\Domains\Core\Auth\Requests\ResetPasswordRequest; use App\Domains\Auth\Requests\ResetPasswordRequest;
use App\Domains\Core\Auth\Services\ResetPasswordAttemptService; use App\Domains\Auth\Services\ResetPasswordAttemptService;
use App\Domains\Core\Authorization\Enums\RoleCode;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
@@ -27,7 +26,6 @@ class ResetPasswordController extends Controller
$data['email'], $data['email'],
$data['codigo'], $data['codigo'],
$data['password'], $data['password'],
RoleCode::from($request->route('reset_role', RoleCode::User->value)),
)) { )) {
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'codigo' => __('api.auth.password_reset_invalid'), 'codigo' => __('api.auth.password_reset_invalid'),

View File

@@ -1,10 +1,10 @@
<?php <?php
namespace App\Domains\Core\Auth\Controllers; namespace App\Domains\Auth\Controllers;
use App\Domains\Core\Auth\Requests\ScannerLoginRequest; use App\Domains\Auth\Requests\ScannerLoginRequest;
use App\Domains\Core\Auth\Resources\UserResource; use App\Domains\Auth\Resources\UserResource;
use App\Domains\Core\Auth\Services\PasswordLoginService; use App\Domains\Auth\Services\PasswordLoginService;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;

View File

@@ -1,10 +1,10 @@
<?php <?php
namespace App\Domains\Core\Auth\Controllers; namespace App\Domains\Auth\Controllers;
use App\Domains\Core\Auth\Models\User; use App\Domains\Auth\Models\User;
use App\Domains\Core\Auth\Resources\ScannerMeResource; use App\Domains\Auth\Resources\ScannerMeResource;
use App\Domains\Core\Auth\Services\ScannerContextService; use App\Domains\Auth\Services\ScannerContextService;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\Request; use Illuminate\Http\Request;

View File

@@ -1,10 +1,10 @@
<?php <?php
namespace App\Domains\Core\Auth\Controllers; namespace App\Domains\Auth\Controllers;
use App\Domains\Core\Auth\Requests\UpdateProfileRequest; use App\Domains\Auth\Requests\UpdateProfileRequest;
use App\Domains\Core\Auth\Resources\UserResource; use App\Domains\Auth\Resources\UserResource;
use App\Domains\Core\Auth\Services\ProfileService; use App\Domains\Auth\Services\ProfileService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
class UpdateProfileController class UpdateProfileController

View File

@@ -1,11 +1,10 @@
<?php <?php
namespace App\Domains\Core\Auth\Controllers; namespace App\Domains\Auth\Controllers;
use App\Domains\Core\Auth\Models\ResetPasswordAttempt; use App\Domains\Auth\Models\ResetPasswordAttempt;
use App\Domains\Core\Auth\Requests\ValidateResetPasswordAttemptRequest; use App\Domains\Auth\Requests\ValidateResetPasswordAttemptRequest;
use App\Domains\Core\Auth\Services\ResetPasswordAttemptService; use App\Domains\Auth\Services\ResetPasswordAttemptService;
use App\Domains\Core\Authorization\Enums\RoleCode;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
@@ -26,7 +25,6 @@ class ValidateResetPasswordAttemptController extends Controller
$result = $this->resetPasswordAttemptService->validateCode( $result = $this->resetPasswordAttemptService->validateCode(
$data['email'], $data['email'],
$data['codigo'], $data['codigo'],
RoleCode::from($request->route('reset_role', RoleCode::User->value)),
); );
if ($result === ResetPasswordAttemptService::CODE_EXPIRED) { if ($result === ResetPasswordAttemptService::CODE_EXPIRED) {

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Core\Auth\Exceptions; namespace App\Domains\Auth\Exceptions;
use Carbon\CarbonImmutable; use Carbon\CarbonImmutable;
use RuntimeException; use RuntimeException;

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Core\Auth\Models; namespace App\Domains\Auth\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Core\Auth\Models; namespace App\Domains\Auth\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Hidden; use Illuminate\Database\Eloquent\Attributes\Hidden;
@@ -17,8 +17,6 @@ class ResetPasswordAttempt extends Model
public const REASON_STAFF_CREATED = 'staff_created'; public const REASON_STAFF_CREATED = 'staff_created';
public const REASON_ADMINISTRATOR_CREATED = 'administrator_created';
public const STATUS_PENDING = 'pending'; public const STATUS_PENDING = 'pending';
public const STATUS_VALIDATED = 'validated'; public const STATUS_VALIDATED = 'validated';

View File

@@ -1,13 +1,11 @@
<?php <?php
namespace App\Domains\Core\Auth\Models; namespace App\Domains\Auth\Models;
use App\Domains\Core\Authorization\Enums\RoleCode; use App\Domains\Authorization\Enums\RoleCode;
use App\Domains\Core\Authorization\Models\Role; use App\Domains\Authorization\Models\Role;
use App\Domains\Commerce\Catalog\Models\Category; use App\Domains\Catalog\Models\Category;
use App\Domains\Ticketing\Event\Models\EventDateChangeView; use App\Domains\Tenant\Models\Tenant;
use App\Domains\Core\Tenant\Models\Tenant;
use App\Domains\Ticketing\Ticket\Models\ScanAttempt;
use Database\Factories\UserFactory; use Database\Factories\UserFactory;
use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Hidden; use Illuminate\Database\Eloquent\Attributes\Hidden;
@@ -15,17 +13,16 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens; use Laravel\Sanctum\HasApiTokens;
#[Fillable(['nombre_apellido', 'email', 'password', 'dni', 'telefono', 'google_id', 'rol_codigo', 'tenant_codigo'])] #[Fillable(['nombre_apellido', 'email', 'password', 'dni', 'telefono', 'google_id', 'rol_codigo', 'tenant_codigo'])]
#[Hidden(['password', 'remember_token', 'active_email', 'active_google_id'])] #[Hidden(['password', 'remember_token'])]
class User extends Authenticatable class User extends Authenticatable
{ {
/** @use HasFactory<UserFactory> */ /** @use HasFactory<UserFactory> */
use HasApiTokens, HasFactory, Notifiable, SoftDeletes; use HasApiTokens, HasFactory, Notifiable;
protected $attributes = [ protected $attributes = [
'rol_codigo' => RoleCode::User->value, 'rol_codigo' => RoleCode::User->value,
@@ -48,18 +45,6 @@ class User extends Authenticatable
return $this->hasMany(LoginAttempt::class); return $this->hasMany(LoginAttempt::class);
} }
/** @return HasMany<ScanAttempt, $this> */
public function scanAttempts(): HasMany
{
return $this->hasMany(ScanAttempt::class, 'scanner_user_id');
}
/** @return HasMany<EventDateChangeView, $this> */
public function eventDateChangeViews(): HasMany
{
return $this->hasMany(EventDateChangeView::class);
}
/** /**
* @return BelongsTo<Role, $this> * @return BelongsTo<Role, $this>
*/ */

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Core\Auth\Requests; namespace App\Domains\Auth\Requests;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str; use Illuminate\Support\Str;

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Core\Auth\Requests; namespace App\Domains\Auth\Requests;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str; use Illuminate\Support\Str;

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Core\Auth\Requests; namespace App\Domains\Auth\Requests;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str; use Illuminate\Support\Str;

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Core\Auth\Requests; namespace App\Domains\Auth\Requests;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Core\Auth\Requests; namespace App\Domains\Auth\Requests;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str; use Illuminate\Support\Str;

View File

@@ -1,8 +1,7 @@
<?php <?php
namespace App\Domains\Core\Auth\Requests; namespace App\Domains\Auth\Requests;
use App\Domains\Core\Authorization\Enums\RoleCode;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Password; use Illuminate\Validation\Rules\Password;
@@ -14,26 +13,15 @@ class RegisterUserRequest extends FormRequest
return true; return true;
} }
protected function prepareForValidation(): void /**
{ * @return array<string, mixed>
if (is_string($this->input('email'))) { */
$this->merge(['email' => mb_strtolower(trim($this->input('email')))]);
}
}
/** @return array<string, mixed> */
public function rules(): array public function rules(): array
{ {
return [ return [
'tenant_codigo' => ['nullable', 'string', Rule::exists('tenants', 'codigo')], 'tenant_codigo' => ['nullable', 'string', Rule::exists('tenants', 'codigo')],
'nombre_apellido' => ['required', 'string', 'max:255'], 'nombre_apellido' => ['required', 'string', 'max:255'],
'email' => [ 'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users', 'email')],
'required',
'string',
'email',
'max:255',
Rule::unique('users', 'active_email')->where('rol_codigo', RoleCode::User->value)->whereNull('deleted_at'),
],
'password' => ['required', 'string', 'confirmed', Password::min(8)->mixedCase()->symbols()], 'password' => ['required', 'string', 'confirmed', Password::min(8)->mixedCase()->symbols()],
'dni' => ['nullable', 'string', 'max:255'], 'dni' => ['nullable', 'string', 'max:255'],
'telefono' => ['nullable', 'string', 'max:255'], 'telefono' => ['nullable', 'string', 'max:255'],

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Core\Auth\Requests; namespace App\Domains\Auth\Requests;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str; use Illuminate\Support\Str;

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Core\Auth\Requests; namespace App\Domains\Auth\Requests;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str; use Illuminate\Support\Str;

View File

@@ -1,5 +1,5 @@
<?php <?php
namespace App\Domains\Core\Auth\Requests; namespace App\Domains\Auth\Requests;
class ScannerLoginRequest extends AdminAppLoginRequest {} class ScannerLoginRequest extends AdminAppLoginRequest {}

View File

@@ -1,10 +1,9 @@
<?php <?php
namespace App\Domains\Core\Auth\Requests; namespace App\Domains\Auth\Requests;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Password;
class UpdateProfileRequest extends FormRequest class UpdateProfileRequest extends FormRequest
{ {
@@ -13,13 +12,6 @@ class UpdateProfileRequest extends FormRequest
return true; return true;
} }
protected function prepareForValidation(): void
{
if (is_string($this->input('email'))) {
$this->merge(['email' => mb_strtolower(trim($this->input('email')))]);
}
}
public function rules(): array public function rules(): array
{ {
return [ return [
@@ -27,13 +19,11 @@ class UpdateProfileRequest extends FormRequest
'email' => [ 'email' => [
'required', 'required',
'email', 'email',
Rule::unique('users', 'active_email')->where('rol_codigo', $this->user()->rol_codigo) Rule::unique('users', 'email')->ignore($this->user()->id),
->whereNull('deleted_at')
->ignore($this->user()->id),
], ],
'dni' => ['nullable', 'string', 'regex:/^[0-9]{7,8}$/'], 'dni' => ['nullable', 'string', 'regex:/^[0-9]{7,8}$/'],
'telefono' => ['nullable', 'string', 'regex:/^\+?[0-9\s\-]+$/'], 'telefono' => ['nullable', 'string', 'regex:/^\+?[0-9\s\-]+$/'],
'password' => ['nullable', 'string', Password::min(8)->mixedCase()->symbols()], 'password' => ['nullable', 'string', \Illuminate\Validation\Rules\Password::min(8)->mixedCase()->symbols()],
]; ];
} }
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Core\Auth\Requests; namespace App\Domains\Auth\Requests;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str; use Illuminate\Support\Str;

View File

@@ -1,9 +1,9 @@
<?php <?php
namespace App\Domains\Core\Auth\Resources; namespace App\Domains\Auth\Resources;
use App\Domains\Core\Auth\Models\User; use App\Domains\Auth\Models\User;
use App\Domains\Core\Tenant\Resources\TenantResource; use App\Domains\Tenant\Resources\TenantResource;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\JsonResource;

View File

@@ -1,9 +1,9 @@
<?php <?php
namespace App\Domains\Core\Auth\Resources; namespace App\Domains\Auth\Resources;
use App\Domains\Core\Auth\Models\User; use App\Domains\Auth\Models\User;
use App\Domains\Core\Tenant\Resources\TenantResource; use App\Domains\Tenant\Resources\TenantResource;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\JsonResource;

View File

@@ -1,8 +1,8 @@
<?php <?php
namespace App\Domains\Core\Auth\Resources; namespace App\Domains\Auth\Resources;
use App\Domains\Core\Auth\Models\User; use App\Domains\Auth\Models\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\JsonResource;
@@ -24,11 +24,6 @@ class UserResource extends JsonResource
'telefono' => $this->telefono, 'telefono' => $this->telefono,
'rol_codigo' => $this->rol_codigo, 'rol_codigo' => $this->rol_codigo,
'tenant_codigo' => $this->tenant_codigo, 'tenant_codigo' => $this->tenant_codigo,
'categories' => $this->whenLoaded('scanCategories', fn () => $this->scanCategories
->map(fn ($category) => [
'id' => $category->id,
'nombre' => $category->nombre,
])->values()),
]; ];
} }
} }

View File

@@ -1,9 +1,9 @@
<?php <?php
namespace App\Domains\Core\Auth\Services; namespace App\Domains\Auth\Services;
use App\Domains\Core\Auth\Models\User; use App\Domains\Auth\Models\User;
use App\Domains\Core\Authorization\Enums\RoleCode; use App\Domains\Authorization\Enums\RoleCode;
class AdminAppContextService class AdminAppContextService
{ {

View File

@@ -1,9 +1,9 @@
<?php <?php
namespace App\Domains\Core\Auth\Services; namespace App\Domains\Auth\Services;
use App\Domains\Core\Auth\Models\User; use App\Domains\Auth\Models\User;
use App\Domains\Core\Authorization\Enums\RoleCode; use App\Domains\Authorization\Enums\RoleCode;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
class AdminCredentialVerifier class AdminCredentialVerifier
@@ -11,7 +11,7 @@ class AdminCredentialVerifier
public function verify(string $email, string $password): bool public function verify(string $email, string $password): bool
{ {
$admin = User::query() $admin = User::query()
->where('active_email', mb_strtolower(trim($email))) ->where('email', mb_strtolower(trim($email)))
->where('rol_codigo', RoleCode::Admin->value) ->where('rol_codigo', RoleCode::Admin->value)
->first(); ->first();

View File

@@ -1,12 +1,11 @@
<?php <?php
namespace App\Domains\Core\Auth\Services; namespace App\Domains\Auth\Services;
use App\Domains\Core\Auth\Models\User; use App\Domains\Auth\Models\User;
use App\Domains\Core\Authorization\Enums\RoleCode; use App\Domains\Notification\Events\UserRegistered;
use App\Shared\Notification\Events\UserRegistered; use App\Domains\Tenant\Models\Tenant;
use App\Domains\Core\Tenant\Models\Tenant; use App\Domains\Tenant\Support\TenantDomainNormalizer;
use App\Domains\Core\Tenant\Support\TenantDomainNormalizer;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
@@ -125,12 +124,12 @@ class GoogleAuthService
]); ]);
} }
$user = User::query()->where('rol_codigo', RoleCode::User->value)->where('google_id', $googleId)->first(); $user = User::query()->where('google_id', $googleId)->first();
if ($user) { if ($user) {
return $user; return $user;
} }
$user = User::query()->where('rol_codigo', RoleCode::User->value)->where('active_email', mb_strtolower(trim($email)))->first(); $user = User::query()->where('email', $email)->first();
if ($user) { if ($user) {
$user->forceFill(['google_id' => $googleId])->save(); $user->forceFill(['google_id' => $googleId])->save();

View File

@@ -1,14 +1,14 @@
<?php <?php
namespace App\Domains\Core\Auth\Services; namespace App\Domains\Auth\Services;
use App\Domains\Core\Auth\Exceptions\AccountLockedException; use App\Domains\Auth\Exceptions\AccountLockedException;
use App\Domains\Core\Auth\Models\LoginAttempt; use App\Domains\Auth\Models\LoginAttempt;
use App\Domains\Core\Auth\Models\ResetPasswordAttempt; use App\Domains\Auth\Models\ResetPasswordAttempt;
use App\Domains\Core\Auth\Models\User; use App\Domains\Auth\Models\User;
use App\Domains\Core\Authorization\Enums\PermissionCode; use App\Domains\Authorization\Enums\PermissionCode;
use App\Domains\Core\Authorization\Enums\RoleCode; use App\Domains\Authorization\Enums\RoleCode;
use App\Shared\Notification\Events\PasswordResetRequested; use App\Domains\Notification\Events\PasswordResetRequested;
use Carbon\CarbonImmutable; use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
@@ -85,7 +85,7 @@ class PasswordLoginService
null, null,
$ipAddress, $ipAddress,
$userAgent, $userAgent,
RoleCode::Scanner, null,
true, true,
PermissionCode::ScanTickets->value, PermissionCode::ScanTickets->value,
PasswordResetRequested::CHANNEL_SCANNER, PasswordResetRequested::CHANNEL_SCANNER,
@@ -120,7 +120,7 @@ class PasswordLoginService
$passwordResetChannel, $passwordResetChannel,
): array { ): array {
$user = User::query() $user = User::query()
->where('active_email', $normalizedEmail) ->where('email', $normalizedEmail)
->when( ->when(
$requiredRole !== null, $requiredRole !== null,
fn ($query) => $query->where('rol_codigo', $requiredRole->value), fn ($query) => $query->where('rol_codigo', $requiredRole->value),

View File

@@ -1,8 +1,8 @@
<?php <?php
namespace App\Domains\Core\Auth\Services; namespace App\Domains\Auth\Services;
use App\Domains\Core\Auth\Models\User; use App\Domains\Auth\Models\User;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
class ProfileService class ProfileService

View File

@@ -1,9 +1,9 @@
<?php <?php
namespace App\Domains\Core\Auth\Services; namespace App\Domains\Auth\Services;
use App\Domains\Core\Auth\Models\User; use App\Domains\Auth\Models\User;
use App\Shared\Notification\Events\UserRegistered; use App\Domains\Notification\Events\UserRegistered;
class RegisterUserService class RegisterUserService
{ {

View File

@@ -1,11 +1,11 @@
<?php <?php
namespace App\Domains\Core\Auth\Services; namespace App\Domains\Auth\Services;
use App\Domains\Core\Auth\Models\ResetPasswordAttempt; use App\Domains\Auth\Models\ResetPasswordAttempt;
use App\Domains\Core\Auth\Models\User; use App\Domains\Auth\Models\User;
use App\Domains\Core\Authorization\Enums\RoleCode; use App\Domains\Authorization\Enums\RoleCode;
use App\Shared\Notification\Events\PasswordResetRequested; use App\Domains\Notification\Events\PasswordResetRequested;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Throwable; use Throwable;
@@ -28,8 +28,7 @@ class ResetPasswordAttemptService
try { try {
$attemptId = DB::transaction(function () use ($email, $emailFingerprint, $reason): ?int { $attemptId = DB::transaction(function () use ($email, $emailFingerprint, $reason): ?int {
$user = User::query() $user = User::query()
->where('active_email', mb_strtolower(trim($email))) ->where('email', $email)
->where('rol_codigo', RoleCode::User->value)
->lockForUpdate() ->lockForUpdate()
->first(); ->first();
@@ -66,7 +65,7 @@ class ResetPasswordAttemptService
try { try {
$result = DB::transaction(function () use ($email, $emailFingerprint, $reason): ?array { $result = DB::transaction(function () use ($email, $emailFingerprint, $reason): ?array {
$user = User::query() $user = User::query()
->where('active_email', mb_strtolower(trim($email))) ->where('email', $email)
->where('rol_codigo', RoleCode::AdminApp->value) ->where('rol_codigo', RoleCode::AdminApp->value)
->whereNotNull('tenant_codigo') ->whereNotNull('tenant_codigo')
->lockForUpdate() ->lockForUpdate()
@@ -114,7 +113,7 @@ class ResetPasswordAttemptService
try { try {
$result = DB::transaction(function () use ($email, $emailFingerprint, $reason): ?array { $result = DB::transaction(function () use ($email, $emailFingerprint, $reason): ?array {
$user = User::query() $user = User::query()
->where('active_email', mb_strtolower(trim($email))) ->where('email', $email)
->where('rol_codigo', RoleCode::Scanner->value) ->where('rol_codigo', RoleCode::Scanner->value)
->whereNotNull('tenant_codigo') ->whereNotNull('tenant_codigo')
->lockForUpdate() ->lockForUpdate()
@@ -153,15 +152,14 @@ class ResetPasswordAttemptService
); );
} }
public function validateCode(string $email, string $code, RoleCode $role = RoleCode::User): string public function validateCode(string $email, string $code): string
{ {
$emailFingerprint = $this->emailFingerprint($email); $emailFingerprint = $this->emailFingerprint($email);
try { try {
return DB::transaction(function () use ($email, $code, $emailFingerprint, $role): string { return DB::transaction(function () use ($email, $code, $emailFingerprint): string {
$user = User::query() $user = User::query()
->where('active_email', mb_strtolower(trim($email))) ->where('email', $email)
->where('rol_codigo', $role->value)
->lockForUpdate() ->lockForUpdate()
->first(); ->first();
@@ -209,15 +207,14 @@ class ResetPasswordAttemptService
} }
} }
public function resetPassword(string $email, string $code, string $password, RoleCode $role = RoleCode::User): bool public function resetPassword(string $email, string $code, string $password): bool
{ {
$emailFingerprint = $this->emailFingerprint($email); $emailFingerprint = $this->emailFingerprint($email);
try { try {
return DB::transaction(function () use ($email, $code, $password, $emailFingerprint, $role): bool { return DB::transaction(function () use ($email, $code, $password, $emailFingerprint): bool {
$user = User::query() $user = User::query()
->where('active_email', mb_strtolower(trim($email))) ->where('email', $email)
->where('rol_codigo', $role->value)
->lockForUpdate() ->lockForUpdate()
->first(); ->first();

View File

@@ -1,8 +1,8 @@
<?php <?php
namespace App\Domains\Core\Auth\Services; namespace App\Domains\Auth\Services;
use App\Domains\Core\Auth\Models\User; use App\Domains\Auth\Models\User;
class ScannerContextService class ScannerContextService
{ {
@@ -19,16 +19,6 @@ class ScannerContextService
$user->setRelation('tenant', $tenant); $user->setRelation('tenant', $tenant);
if ($tenant->requiresScannerCategoryValidation()) {
$categories = $user->scanCategories()
->orderBy('nombre')
->get();
if ($categories->isNotEmpty()) {
$user->setRelation('scanCategories', $categories);
}
}
return $user; return $user;
} }
} }

View File

@@ -1,10 +1,10 @@
<?php <?php
use App\Domains\Core\Auth\Controllers\AdminAppLoginController; use App\Domains\Auth\Controllers\AdminAppLoginController;
use App\Domains\Core\Auth\Controllers\AdminAppMeController; use App\Domains\Auth\Controllers\AdminAppMeController;
use App\Domains\Core\Auth\Controllers\CreateAdminAppResetPasswordAttemptController; use App\Domains\Auth\Controllers\CreateAdminAppResetPasswordAttemptController;
use App\Domains\Core\Auth\Controllers\ResetPasswordController; use App\Domains\Auth\Controllers\ResetPasswordController;
use App\Domains\Core\Auth\Controllers\ValidateResetPasswordAttemptController; use App\Domains\Auth\Controllers\ValidateResetPasswordAttemptController;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
Route::prefix('v1/adminapp')->group(function (): void { Route::prefix('v1/adminapp')->group(function (): void {
@@ -12,10 +12,8 @@ Route::prefix('v1/adminapp')->group(function (): void {
Route::post('password/reset-attempts', CreateAdminAppResetPasswordAttemptController::class) Route::post('password/reset-attempts', CreateAdminAppResetPasswordAttemptController::class)
->middleware('throttle:5,1'); ->middleware('throttle:5,1');
Route::post('password/reset-attempts/validate', ValidateResetPasswordAttemptController::class) Route::post('password/reset-attempts/validate', ValidateResetPasswordAttemptController::class)
->defaults('reset_role', 'adminapp')
->middleware('throttle:10,1'); ->middleware('throttle:10,1');
Route::post('password/reset', ResetPasswordController::class) Route::post('password/reset', ResetPasswordController::class)
->defaults('reset_role', 'adminapp')
->middleware('throttle:5,1'); ->middleware('throttle:5,1');
Route::middleware(['auth:sanctum', 'adminapp.tenant']) Route::middleware(['auth:sanctum', 'adminapp.tenant'])
->get('me', AdminAppMeController::class); ->get('me', AdminAppMeController::class);

View File

@@ -1,14 +1,14 @@
<?php <?php
use App\Domains\Core\Auth\Controllers\CreateResetPasswordAttemptController; use App\Domains\Auth\Controllers\CreateResetPasswordAttemptController;
use App\Domains\Core\Auth\Controllers\GoogleTokenExchangeController; use App\Domains\Auth\Controllers\GoogleTokenExchangeController;
use App\Domains\Core\Auth\Controllers\LoginController; use App\Domains\Auth\Controllers\LoginController;
use App\Domains\Core\Auth\Controllers\LogoutController; use App\Domains\Auth\Controllers\LogoutController;
use App\Domains\Core\Auth\Controllers\MeController; use App\Domains\Auth\Controllers\MeController;
use App\Domains\Core\Auth\Controllers\RegisterController; use App\Domains\Auth\Controllers\RegisterController;
use App\Domains\Core\Auth\Controllers\ResetPasswordController; use App\Domains\Auth\Controllers\ResetPasswordController;
use App\Domains\Core\Auth\Controllers\UpdateProfileController; use App\Domains\Auth\Controllers\UpdateProfileController;
use App\Domains\Core\Auth\Controllers\ValidateResetPasswordAttemptController; use App\Domains\Auth\Controllers\ValidateResetPasswordAttemptController;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
Route::post('/register', RegisterController::class); Route::post('/register', RegisterController::class);

View File

@@ -1,10 +1,10 @@
<?php <?php
use App\Domains\Core\Auth\Controllers\CreateScannerResetPasswordAttemptController; use App\Domains\Auth\Controllers\CreateScannerResetPasswordAttemptController;
use App\Domains\Core\Auth\Controllers\ResetPasswordController; use App\Domains\Auth\Controllers\ResetPasswordController;
use App\Domains\Core\Auth\Controllers\ScannerLoginController; use App\Domains\Auth\Controllers\ScannerLoginController;
use App\Domains\Core\Auth\Controllers\ScannerMeController; use App\Domains\Auth\Controllers\ScannerMeController;
use App\Domains\Core\Auth\Controllers\ValidateResetPasswordAttemptController; use App\Domains\Auth\Controllers\ValidateResetPasswordAttemptController;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
Route::prefix('v1/scanner')->group(function (): void { Route::prefix('v1/scanner')->group(function (): void {
@@ -12,10 +12,8 @@ Route::prefix('v1/scanner')->group(function (): void {
Route::post('password/reset-attempts', CreateScannerResetPasswordAttemptController::class) Route::post('password/reset-attempts', CreateScannerResetPasswordAttemptController::class)
->middleware('throttle:5,1'); ->middleware('throttle:5,1');
Route::post('password/reset-attempts/validate', ValidateResetPasswordAttemptController::class) Route::post('password/reset-attempts/validate', ValidateResetPasswordAttemptController::class)
->defaults('reset_role', 'scanner')
->middleware('throttle:10,1'); ->middleware('throttle:10,1');
Route::post('password/reset', ResetPasswordController::class) Route::post('password/reset', ResetPasswordController::class)
->defaults('reset_role', 'scanner')
->middleware('throttle:5,1'); ->middleware('throttle:5,1');
Route::middleware(['auth:sanctum', 'scanner.tenant']) Route::middleware(['auth:sanctum', 'scanner.tenant'])
->get('me', ScannerMeController::class); ->get('me', ScannerMeController::class);

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Core\Authorization\Enums; namespace App\Domains\Authorization\Enums;
enum PermissionCode: string enum PermissionCode: string
{ {

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Core\Authorization\Enums; namespace App\Domains\Authorization\Enums;
enum RoleCode: string enum RoleCode: string
{ {

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Core\Authorization\Models; namespace App\Domains\Authorization\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;

View File

@@ -1,10 +1,10 @@
<?php <?php
namespace App\Domains\Core\Authorization\Models; namespace App\Domains\Authorization\Models;
use App\Domains\Core\Auth\Models\User; use App\Domains\Auth\Models\User;
use App\Domains\Core\Menu\Models\Menu; use App\Domains\Menu\Models\Menu;
use App\Domains\Core\Menu\Models\MenuRole; use App\Domains\Menu\Models\MenuRole;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Core\Authorization\Models; namespace App\Domains\Authorization\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;

View File

@@ -1,10 +1,10 @@
<?php <?php
namespace App\Domains\Core\Bootstrap\Controllers; namespace App\Domains\Bootstrap\Controllers;
use App\Domains\Core\Bootstrap\Requests\AdminAppBootstrapRequest; use App\Domains\Bootstrap\Requests\AdminAppBootstrapRequest;
use App\Domains\Core\Bootstrap\Resources\AdminAppBootstrapResource; use App\Domains\Bootstrap\Resources\AdminAppBootstrapResource;
use App\Domains\Core\Bootstrap\Services\AdminAppBootstrapService; use App\Domains\Bootstrap\Services\AdminAppBootstrapService;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
class AdminAppBootstrapController extends Controller class AdminAppBootstrapController extends Controller

View File

@@ -1,10 +1,10 @@
<?php <?php
namespace App\Domains\Core\Bootstrap\Controllers; namespace App\Domains\Bootstrap\Controllers;
use App\Domains\Core\Bootstrap\Requests\ScannerBootstrapRequest; use App\Domains\Bootstrap\Requests\ScannerBootstrapRequest;
use App\Domains\Core\Bootstrap\Resources\ScannerBootstrapResource; use App\Domains\Bootstrap\Resources\ScannerBootstrapResource;
use App\Domains\Core\Bootstrap\Services\ScannerBootstrapService; use App\Domains\Bootstrap\Services\ScannerBootstrapService;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
class ScannerBootstrapController extends Controller class ScannerBootstrapController extends Controller

View File

@@ -1,10 +1,10 @@
<?php <?php
namespace App\Domains\Core\Bootstrap\Controllers; namespace App\Domains\Bootstrap\Controllers;
use App\Domains\Core\Bootstrap\Requests\TenantBootstrapRequest; use App\Domains\Bootstrap\Requests\TenantBootstrapRequest;
use App\Domains\Core\Bootstrap\Services\TenantBootstrapService; use App\Domains\Bootstrap\Services\TenantBootstrapService;
use App\Domains\Core\Tenant\Resources\TenantResource; use App\Domains\Tenant\Resources\TenantResource;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
class TenantBootstrapController extends Controller class TenantBootstrapController extends Controller

View File

@@ -1,5 +1,5 @@
<?php <?php
namespace App\Domains\Core\Bootstrap\Requests; namespace App\Domains\Bootstrap\Requests;
class AdminAppBootstrapRequest extends TenantBootstrapRequest {} class AdminAppBootstrapRequest extends TenantBootstrapRequest {}

View File

@@ -1,5 +1,5 @@
<?php <?php
namespace App\Domains\Core\Bootstrap\Requests; namespace App\Domains\Bootstrap\Requests;
class ScannerBootstrapRequest extends TenantBootstrapRequest {} class ScannerBootstrapRequest extends TenantBootstrapRequest {}

View File

@@ -1,8 +1,8 @@
<?php <?php
namespace App\Domains\Core\Bootstrap\Requests; namespace App\Domains\Bootstrap\Requests;
use App\Domains\Core\Tenant\Support\TenantDomainNormalizer; use App\Domains\Tenant\Support\TenantDomainNormalizer;
use Closure; use Closure;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;

View File

@@ -1,23 +1,22 @@
<?php <?php
namespace App\Domains\Core\Bootstrap\Resources; namespace App\Domains\Bootstrap\Resources;
use App\Domains\Core\Tenant\Models\AdminWebsiteType; use App\Domains\Tenant\Models\WebsiteType;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\JsonResource;
/** @mixin array{website_type: AdminWebsiteType} */ /** @mixin array{website_type: WebsiteType} */
class AdminAppBootstrapResource extends JsonResource class AdminAppBootstrapResource extends JsonResource
{ {
/** @return array<string, mixed> */ /** @return array<string, mixed> */
public function toArray(Request $request): array public function toArray(Request $request): array
{ {
/** @var AdminWebsiteType $websiteType */ /** @var WebsiteType $websiteType */
$websiteType = $this->resource['website_type']; $websiteType = $this->resource['website_type'];
return [ return [
'admin_website_type_code' => $websiteType->codigo, 'website_type_code' => $websiteType->codigo,
'site_title' => $websiteType->site_title,
'primary_color' => $websiteType->primary_color, 'primary_color' => $websiteType->primary_color,
'secondary_color' => $websiteType->secondary_color, 'secondary_color' => $websiteType->secondary_color,
'danger_color' => $websiteType->danger_color, 'danger_color' => $websiteType->danger_color,

View File

@@ -1,5 +1,5 @@
<?php <?php
namespace App\Domains\Core\Bootstrap\Resources; namespace App\Domains\Bootstrap\Resources;
class ScannerBootstrapResource extends AdminAppBootstrapResource {} class ScannerBootstrapResource extends AdminAppBootstrapResource {}

View File

@@ -1,16 +1,16 @@
<?php <?php
namespace App\Domains\Core\Bootstrap\Services; namespace App\Domains\Bootstrap\Services;
use App\Domains\Core\Tenant\Models\AdminWebsiteType; use App\Domains\Tenant\Models\WebsiteType;
class AdminAppBootstrapService class AdminAppBootstrapService
{ {
/** @return array{website_type: AdminWebsiteType} */ /** @return array{website_type: WebsiteType} */
public function get(string $domain): array public function get(string $domain): array
{ {
return [ return [
'website_type' => AdminWebsiteType::query() 'website_type' => WebsiteType::query()
->with(['siteLogo', 'footerLogo', 'favicon']) ->with(['siteLogo', 'footerLogo', 'favicon'])
->where('dominio', $domain) ->where('dominio', $domain)
->firstOrFail(), ->firstOrFail(),

View File

@@ -1,16 +1,16 @@
<?php <?php
namespace App\Domains\Core\Bootstrap\Services; namespace App\Domains\Bootstrap\Services;
use App\Domains\Core\Tenant\Models\AdminWebsiteType; use App\Domains\Tenant\Models\WebsiteType;
class ScannerBootstrapService class ScannerBootstrapService
{ {
/** @return array{website_type: AdminWebsiteType} */ /** @return array{website_type: WebsiteType} */
public function get(string $domain): array public function get(string $domain): array
{ {
return [ return [
'website_type' => AdminWebsiteType::query() 'website_type' => WebsiteType::query()
->with(['siteLogo', 'footerLogo', 'favicon']) ->with(['siteLogo', 'footerLogo', 'favicon'])
->where('scanner_domain', $domain) ->where('scanner_domain', $domain)
->firstOrFail(), ->firstOrFail(),

View File

@@ -1,11 +1,11 @@
<?php <?php
namespace App\Domains\Core\Bootstrap\Services; namespace App\Domains\Bootstrap\Services;
use App\Domains\Core\Authorization\Enums\RoleCode; use App\Domains\Authorization\Enums\RoleCode;
use App\Domains\Core\Tenant\Models\Tenant; use App\Domains\Tenant\Models\Tenant;
use App\Domains\Core\Tenant\Services\TenantInformationService; use App\Domains\Tenant\Services\TenantInformationService;
use App\Domains\Core\Tenant\Support\TenantDomainNormalizer; use App\Domains\Tenant\Support\TenantDomainNormalizer;
use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\Eloquent\ModelNotFoundException;
class TenantBootstrapService class TenantBootstrapService
@@ -32,13 +32,11 @@ class TenantBootstrapService
return $this->tenantInformationService->load( return $this->tenantInformationService->load(
$tenant, $tenant,
[ [
'eventDateChanges',
'menues' => fn ($query) => $query->whereHas( 'menues' => fn ($query) => $query->whereHas(
'roles', 'roles',
fn ($query) => $query->where('codigo', RoleCode::User->value) fn ($query) => $query->where('codigo', RoleCode::User->value)
), ),
$tenant->storefront_website_type_code === 'onticket_multi_event' 'categories' => fn ($query) => $query->orderBy('nombre'),
? 'eventCategories' : 'categories' => fn ($query) => $query->orderBy('nombre'),
] ]
); );
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
use App\Domains\Core\Bootstrap\Controllers\AdminAppBootstrapController; use App\Domains\Bootstrap\Controllers\AdminAppBootstrapController;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
Route::get( Route::get(

View File

@@ -1,6 +1,6 @@
<?php <?php
use App\Domains\Core\Bootstrap\Controllers\TenantBootstrapController; use App\Domains\Bootstrap\Controllers\TenantBootstrapController;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
Route::get('tenants/bootstrap', TenantBootstrapController::class); Route::get('tenants/bootstrap', TenantBootstrapController::class);

View File

@@ -1,6 +1,6 @@
<?php <?php
use App\Domains\Core\Bootstrap\Controllers\ScannerBootstrapController; use App\Domains\Bootstrap\Controllers\ScannerBootstrapController;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
Route::get( Route::get(

View File

@@ -1,13 +1,13 @@
<?php <?php
namespace App\Domains\Commerce\Cart\Controllers; namespace App\Domains\Cart\Controllers;
use App\Domains\Commerce\Cart\Models\CartItem; use App\Domains\Cart\Models\CartItem;
use App\Domains\Commerce\Cart\Requests\AddCartItemRequest; use App\Domains\Cart\Requests\AddCartItemRequest;
use App\Domains\Commerce\Cart\Requests\UpdateCartItemQuantityRequest; use App\Domains\Cart\Requests\UpdateCartItemQuantityRequest;
use App\Domains\Commerce\Cart\Resources\CartResource; use App\Domains\Cart\Resources\CartResource;
use App\Domains\Commerce\Cart\Services\CartService; use App\Domains\Cart\Services\CartService;
use App\Domains\Core\Tenant\Models\Tenant; use App\Domains\Tenant\Models\Tenant;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;

View File

@@ -1,17 +1,17 @@
<?php <?php
namespace App\Domains\Commerce\Cart\Models; namespace App\Domains\Cart\Models;
use App\Domains\Core\Auth\Models\User; use App\Domains\Auth\Models\User;
use App\Domains\Commerce\Catalog\Models\CatalogItem; use App\Domains\Catalog\Models\CatalogItem;
use App\Domains\Commerce\Catalog\Models\Inventory; use App\Domains\Catalog\Models\Inventory;
use App\Domains\Commerce\Catalog\Models\StockReservation; use App\Domains\Catalog\Models\StockReservation;
use App\Domains\Commerce\Catalog\Models\Variant; use App\Domains\Catalog\Models\Variant;
use App\Domains\Commerce\Catalog\Services\CatalogInventoryService; use App\Domains\Catalog\Services\CatalogInventoryService;
use App\Domains\Commerce\Catalog\Services\StockReservationService; use App\Domains\Catalog\Services\StockReservationService;
use App\Domains\Commerce\Purchase\Models\Purchase; use App\Domains\Purchase\Models\Purchase;
use App\Domains\Commerce\Purchase\Services\UserPurchaseLimitService; use App\Domains\Purchase\Services\UserPurchaseLimitService;
use App\Domains\Core\Tenant\Models\Tenant; use App\Domains\Tenant\Models\Tenant;
use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
@@ -375,7 +375,7 @@ class Cart extends Model
): CatalogItem|Variant { ): CatalogItem|Variant {
$catalogItemQuery = CatalogItem::query() $catalogItemQuery = CatalogItem::query()
->whereKey($catalogItemId) ->whereKey($catalogItemId)
->forTenantCatalog($this->tenant()->firstOrFail()); ->where('tenant_code', $this->tenant_codigo);
if ($lockForUpdate) { if ($lockForUpdate) {
$catalogItemQuery->lockForUpdate(); $catalogItemQuery->lockForUpdate();
@@ -400,17 +400,6 @@ class Cart extends Model
]); ]);
} }
if ($catalogItem->bundleComponents()
->whereNotNull('component_variant_id')
->whereHas('variant', fn ($query) => $query
->whereNotNull('sales_disabled_at')
->orWhereNotNull('replaced_by_variant_id'))
->exists()) {
throw ValidationException::withMessages([
'catalog_item_id' => [__('api.cart.bundle_component_unavailable')],
]);
}
return $catalogItem; return $catalogItem;
} }
@@ -441,12 +430,6 @@ class Cart extends Model
throw new NotFoundHttpException('Variant not found for catalog item.'); throw new NotFoundHttpException('Variant not found for catalog item.');
} }
if (! $variant->isSellable()) {
throw ValidationException::withMessages([
'variant_id' => [__('api.cart.variant_unavailable')],
]);
}
$inventory = $this->resolveInventory($variant->inventory_id, $lockForUpdate); $inventory = $this->resolveInventory($variant->inventory_id, $lockForUpdate);
$variant->setRelation('catalogItem', $catalogItem); $variant->setRelation('catalogItem', $catalogItem);
$variant->setRelation('inventory', $inventory); $variant->setRelation('inventory', $inventory);

View File

@@ -1,9 +1,9 @@
<?php <?php
namespace App\Domains\Commerce\Cart\Models; namespace App\Domains\Cart\Models;
use App\Domains\Commerce\Catalog\Models\CatalogItem; use App\Domains\Catalog\Models\CatalogItem;
use App\Domains\Commerce\Catalog\Models\Variant; use App\Domains\Catalog\Models\Variant;
use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Commerce\Cart\Requests; namespace App\Domains\Cart\Requests;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Commerce\Cart\Requests; namespace App\Domains\Cart\Requests;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;

View File

@@ -1,11 +1,11 @@
<?php <?php
namespace App\Domains\Commerce\Cart\Resources; namespace App\Domains\Cart\Resources;
use App\Domains\Commerce\Cart\Models\CartItem; use App\Domains\Cart\Models\CartItem;
use App\Domains\Commerce\Catalog\Enums\InventoryPolicy; use App\Domains\Catalog\Enums\InventoryPolicy;
use App\Domains\Commerce\Catalog\Models\Variant; use App\Domains\Catalog\Models\Variant;
use App\Domains\Core\Tenant\Models\Tenant; use App\Domains\Tenant\Models\Tenant;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\JsonResource;

View File

@@ -1,8 +1,8 @@
<?php <?php
namespace App\Domains\Commerce\Cart\Resources; namespace App\Domains\Cart\Resources;
use App\Domains\Commerce\Cart\Models\Cart; use App\Domains\Cart\Models\Cart;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\JsonResource;

View File

@@ -1,12 +1,12 @@
<?php <?php
namespace App\Domains\Commerce\Cart\Services; namespace App\Domains\Cart\Services;
use App\Domains\Core\Auth\Models\User; use App\Domains\Auth\Models\User;
use App\Domains\Commerce\Cart\Models\Cart; use App\Domains\Cart\Models\Cart;
use App\Domains\Commerce\Catalog\Exceptions\StockReservationExpiredException; use App\Domains\Catalog\Exceptions\StockReservationExpiredException;
use App\Domains\Commerce\Catalog\Services\ExpireStockReservationsService; use App\Domains\Catalog\Services\ExpireStockReservationsService;
use App\Domains\Core\Tenant\Models\Tenant; use App\Domains\Tenant\Models\Tenant;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;

View File

@@ -1,9 +1,9 @@
<?php <?php
namespace App\Domains\Commerce\Cart\Services; namespace App\Domains\Cart\Services;
use App\Domains\Core\Auth\Models\User; use App\Domains\Auth\Models\User;
use App\Domains\Commerce\Cart\Models\Cart; use App\Domains\Cart\Models\Cart;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
class GuestCartMergeService class GuestCartMergeService

View File

@@ -1,6 +1,6 @@
<?php <?php
use App\Domains\Commerce\Cart\Controllers\CartController; use App\Domains\Cart\Controllers\CartController;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
Route::prefix('tenants/{tenant:codigo}') Route::prefix('tenants/{tenant:codigo}')

View File

@@ -1,25 +1,27 @@
<?php <?php
namespace App\Domains\Commerce\Catalog\Controllers; namespace App\Domains\Catalog\Controllers;
use App\Domains\Commerce\Cart\Services\CartService; use App\Domains\Cart\Services\CartService;
use App\Domains\Commerce\Catalog\Models\CatalogItem; use App\Domains\Catalog\Models\CatalogItem;
use App\Domains\Commerce\Catalog\Models\Category; use App\Domains\Catalog\Models\Category;
use App\Domains\Commerce\Catalog\Models\FeaturedGroup; use App\Domains\Catalog\Models\FeaturedGroup;
use App\Domains\Commerce\Catalog\Requests\CatalogItemDetailRequest; use App\Domains\Catalog\Requests\CatalogItemDetailRequest;
use App\Domains\Commerce\Catalog\Requests\CatalogVariantOptionsRequest; use App\Domains\Catalog\Requests\CatalogVariantOptionsRequest;
use App\Domains\Commerce\Catalog\Requests\CategoryPageRequest; use App\Domains\Catalog\Requests\CategoryPageRequest;
use App\Domains\Commerce\Catalog\Requests\FeaturedGroupPageRequest; use App\Domains\Catalog\Requests\FeaturedGroupPageRequest;
use App\Domains\Commerce\Catalog\Requests\SearchCatalogItemsRequest; use App\Domains\Catalog\Requests\SearchCatalogItemsRequest;
use App\Domains\Commerce\Catalog\Resources\CatalogFeaturedGroupResource; use App\Domains\Catalog\Requests\StoreCatalogItemRequest;
use App\Domains\Commerce\Catalog\Resources\CatalogItemDetailResource; use App\Domains\Catalog\Resources\CatalogFeaturedGroupResource;
use App\Domains\Commerce\Catalog\Resources\CatalogSearchItemResource; use App\Domains\Catalog\Resources\CatalogItemDetailResource;
use App\Domains\Commerce\Catalog\Resources\CatalogVariantOptionsResource; use App\Domains\Catalog\Resources\CatalogItemResource;
use App\Domains\Commerce\Catalog\Services\CatalogItemAllowanceService; use App\Domains\Catalog\Resources\CatalogSearchItemResource;
use App\Domains\Commerce\Catalog\Services\CatalogService; use App\Domains\Catalog\Resources\CatalogVariantOptionsResource;
use App\Domains\Commerce\Catalog\Services\FeaturedGroupService; use App\Domains\Catalog\Services\CatalogItemAllowanceService;
use App\Domains\Commerce\Catalog\Services\VariantSelectionService; use App\Domains\Catalog\Services\CatalogService;
use App\Domains\Core\Tenant\Models\Tenant; use App\Domains\Catalog\Services\FeaturedGroupService;
use App\Domains\Catalog\Services\VariantSelectionService;
use App\Domains\Tenant\Models\Tenant;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -38,7 +40,7 @@ class CatalogController extends Controller
return response()->json($featuredGroups->map( return response()->json($featuredGroups->map(
fn (FeaturedGroup $featuredGroup): array => (new CatalogFeaturedGroupResource( fn (FeaturedGroup $featuredGroup): array => (new CatalogFeaturedGroupResource(
$featuredGroup, $featuredGroup,
$featuredGroupService->itemsResponse($featuredGroup, $tenant, 1, $this->userId($request)), $featuredGroupService->itemsResponse($featuredGroup, 1, $this->userId($request)),
))->resolve() ))->resolve()
)); ));
} }
@@ -100,7 +102,6 @@ class CatalogController extends Controller
return response()->json($featuredGroupService->itemsResponse( return response()->json($featuredGroupService->itemsResponse(
$featuredGroup, $featuredGroup,
$tenant,
$page, $page,
$this->userId($request), $this->userId($request),
)); ));
@@ -113,8 +114,7 @@ class CatalogController extends Controller
CatalogService $catalogService, CatalogService $catalogService,
CatalogItemAllowanceService $allowances, CatalogItemAllowanceService $allowances,
): CatalogItemDetailResource { ): CatalogItemDetailResource {
abort_unless($catalogItem->tenant_code === $tenant->codigo abort_unless($catalogItem->tenant_code === $tenant->codigo, 404);
&& ($tenant->active_event_id === null || $catalogItem->event_id === $tenant->active_event_id), 404);
$variantId = $request->validated('variant_id'); $variantId = $request->validated('variant_id');
@@ -134,8 +134,7 @@ class CatalogController extends Controller
VariantSelectionService $variantSelectionService, VariantSelectionService $variantSelectionService,
CartService $cartService, CartService $cartService,
): CatalogVariantOptionsResource { ): CatalogVariantOptionsResource {
abort_unless($catalogItem->tenant_code === $tenant->codigo abort_unless($catalogItem->tenant_code === $tenant->codigo, 404);
&& ($tenant->active_event_id === null || $catalogItem->event_id === $tenant->active_event_id), 404);
$includedVariantId = null; $includedVariantId = null;
$cartItemId = $request->validated('cart_item_id'); $cartItemId = $request->validated('cart_item_id');
@@ -164,6 +163,21 @@ class CatalogController extends Controller
); );
} }
public function store(
StoreCatalogItemRequest $request,
Tenant $tenant,
CatalogService $catalogService,
): JsonResponse {
$catalogItem = $catalogService->create([
...$request->validated(),
'tenant_code' => $tenant->codigo,
]);
return CatalogItemResource::make($catalogItem)
->response()
->setStatusCode(201);
}
private function userId(Request $request): ?int private function userId(Request $request): ?int
{ {
$userId = $request->user()?->getAuthIdentifier() ?? Auth::guard('sanctum')->id(); $userId = $request->user()?->getAuthIdentifier() ?? Auth::guard('sanctum')->id();

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Commerce\Catalog\Enums; namespace App\Domains\Catalog\Enums;
enum CatalogItemType: string enum CatalogItemType: string
{ {

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Commerce\Catalog\Enums; namespace App\Domains\Catalog\Enums;
enum FeaturedGroupSource: string enum FeaturedGroupSource: string
{ {

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Commerce\Catalog\Enums; namespace App\Domains\Catalog\Enums;
enum GroupLayout: string enum GroupLayout: string
{ {

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Commerce\Catalog\Enums; namespace App\Domains\Catalog\Enums;
enum InventoryPolicy: string enum InventoryPolicy: string
{ {

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Commerce\Catalog\Enums; namespace App\Domains\Catalog\Enums;
enum InventorySubject: string enum InventorySubject: string
{ {

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Commerce\Catalog\Enums; namespace App\Domains\Catalog\Enums;
enum ProductLayout: string enum ProductLayout: string
{ {

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Commerce\Catalog\Exceptions; namespace App\Domains\Catalog\Exceptions;
use RuntimeException; use RuntimeException;

View File

@@ -1,10 +1,10 @@
<?php <?php
namespace App\Domains\Commerce\Catalog\Models; namespace App\Domains\Catalog\Models;
use App\Domains\Ticketing\Event\Models\EventDate; use App\Domains\Event\Models\EventDate;
use App\Shared\Enums\FieldType; use App\Domains\Shared\Enums\FieldType;
use App\Domains\Core\Tenant\Models\Tenant; use App\Domains\Tenant\Models\Tenant;
use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
@@ -59,8 +59,6 @@ class Attribute extends Model
public function eventDates(): HasMany public function eventDates(): HasMany
{ {
return $this->hasMany(EventDate::class, 'tenant_code', 'tenant_codigo') return $this->hasMany(EventDate::class, 'tenant_code', 'tenant_codigo')
->whereNull('rescheduled_to_event_date_id')
->whereNull('suspended_at')
->orderBy('date') ->orderBy('date')
->orderBy('time_start'); ->orderBy('time_start');
} }

View File

@@ -1,8 +1,8 @@
<?php <?php
namespace App\Domains\Commerce\Catalog\Models; namespace App\Domains\Catalog\Models;
use App\Domains\Ticketing\Ticket\Models\ValidityTime; use App\Domains\Ticket\Models\ValidityTime;
use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;

View File

@@ -1,8 +1,8 @@
<?php <?php
namespace App\Domains\Commerce\Catalog\Models; namespace App\Domains\Catalog\Models;
use App\Domains\Core\Tenant\Models\Tenant; use App\Domains\Tenant\Models\Tenant;
use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace App\Domains\Commerce\Catalog\Models; namespace App\Domains\Catalog\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;

View File

@@ -1,15 +1,14 @@
<?php <?php
namespace App\Domains\Commerce\Catalog\Models; namespace App\Domains\Catalog\Models;
use App\Shared\Attachable\Models\Attachment; use App\Domains\Attachable\Models\Attachment;
use App\Domains\Commerce\Catalog\Enums\CatalogItemType; use App\Domains\Catalog\Enums\CatalogItemType;
use App\Domains\Commerce\Catalog\Enums\InventoryPolicy; use App\Domains\Catalog\Enums\InventoryPolicy;
use App\Domains\Commerce\Catalog\Enums\InventorySubject; use App\Domains\Catalog\Enums\InventorySubject;
use App\Domains\Commerce\Catalog\Services\CatalogInventoryService; use App\Domains\Catalog\Services\CatalogInventoryService;
use App\Domains\Core\Tenant\Models\Tenant; use App\Domains\Tenant\Models\Tenant;
use App\Domains\Ticketing\Event\Models\Event; use App\Domains\Ticket\Models\Ticket;
use App\Domains\Ticketing\Ticket\Models\Ticket;
use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -31,7 +30,6 @@ use Illuminate\Support\Collection;
'group_order', 'group_order',
'descripcion', 'descripcion',
'precio', 'precio',
'sales_end_at',
'inventory_policy', 'inventory_policy',
'inventory_subject', 'inventory_subject',
'max_units_per_user', 'max_units_per_user',
@@ -53,30 +51,15 @@ class CatalogItem extends Model
'group_order' => 0, 'group_order' => 0,
]; ];
protected static function booted(): void
{
static::creating(function (CatalogItem $item): void {
if ($item->event_id !== null || $item->tenant_code === null) {
return;
}
$item->event_id = Tenant::query()
->where('codigo', $item->tenant_code)
->value('active_event_id');
});
}
protected function casts(): array protected function casts(): array
{ {
return [ return [
'event_id' => 'integer',
'category_id' => 'integer', 'category_id' => 'integer',
'brand_id' => 'integer', 'brand_id' => 'integer',
'inventory_id' => 'integer', 'inventory_id' => 'integer',
'type' => CatalogItemType::class, 'type' => CatalogItemType::class,
'group_order' => 'integer', 'group_order' => 'integer',
'precio' => 'decimal:2', 'precio' => 'decimal:2',
'sales_end_at' => 'datetime',
'inventory_policy' => InventoryPolicy::class, 'inventory_policy' => InventoryPolicy::class,
'inventory_subject' => InventorySubject::class, 'inventory_subject' => InventorySubject::class,
'max_units_per_user' => 'integer', 'max_units_per_user' => 'integer',
@@ -90,21 +73,6 @@ class CatalogItem extends Model
return $this->belongsTo(Tenant::class, 'tenant_code', 'codigo'); return $this->belongsTo(Tenant::class, 'tenant_code', 'codigo');
} }
/** @return BelongsTo<Event, $this> */
public function event(): BelongsTo
{
return $this->belongsTo(Event::class);
}
/** @param Builder<CatalogItem> $query */
public function scopeForTenantCatalog(Builder $query, Tenant $tenant): Builder
{
return $query
->where('catalog_items.tenant_code', $tenant->codigo)
->when($tenant->active_event_id !== null, fn (Builder $query): Builder => $query
->where('catalog_items.event_id', $tenant->active_event_id));
}
/** @return BelongsTo<Category, $this> */ /** @return BelongsTo<Category, $this> */
public function category(): BelongsTo public function category(): BelongsTo
{ {
@@ -193,10 +161,6 @@ class CatalogItem extends Model
public function isAvailable(): bool public function isAvailable(): bool
{ {
if (! $this->isSaleOpen()) {
return false;
}
if ($this->type === CatalogItemType::Bundle) { if ($this->type === CatalogItemType::Bundle) {
$availableStock = $this->availableStock(); $availableStock = $this->availableStock();
@@ -214,32 +178,13 @@ class CatalogItem extends Model
public function scopeWhereAvailable(Builder $query): Builder public function scopeWhereAvailable(Builder $query): Builder
{ {
return $query->where(function (Builder $query): void { return $query->where(function (Builder $query): void {
$query->whereNull('catalog_items.sales_end_at')
->orWhere('catalog_items.sales_end_at', '>', now());
})->where(function (Builder $query): void {
$query $query
->where(function (Builder $unlimitedQuery): void {
$unlimitedQuery
->where('catalog_items.inventory_policy', InventoryPolicy::Unlimited->value) ->where('catalog_items.inventory_policy', InventoryPolicy::Unlimited->value)
->where(function (Builder $selectionQuery): void {
$selectionQuery
->whereDoesntHave('variants')
->orWhereHas('variants', fn (Builder $variantQuery): Builder => $variantQuery
->whereNull('sales_disabled_at')
->whereNull('replaced_by_variant_id'));
});
})
->orWhereHas( ->orWhereHas(
'variants', 'variants.inventory',
fn (Builder $variantQuery): Builder => $variantQuery
->whereNull('sales_disabled_at')
->whereNull('replaced_by_variant_id')
->whereHas(
'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 { ->orWhere(function (Builder $directItemQuery): void {
$directItemQuery $directItemQuery
->whereDoesntHave('variants') ->whereDoesntHave('variants')
@@ -256,22 +201,13 @@ class CatalogItem extends Model
}); });
} }
public function isSaleOpen(): bool
{
return $this->sales_end_at === null || now()->lt($this->sales_end_at);
}
/** @return Collection<int, Variant> */ /** @return Collection<int, Variant> */
public function visibleVariants(?int $includedVariantId = null): Collection public function visibleVariants(?int $includedVariantId = null): Collection
{ {
return $this->variants return $this->variants
->each(fn (Variant $variant) => $variant->setRelation('catalogItem', $this)) ->filter(fn (Variant $variant): bool => ($includedVariantId !== null && $variant->id === $includedVariantId)
->filter(fn (Variant $variant): bool => $variant->hasOnlyActiveEventDates() || $this->inventory_policy === InventoryPolicy::Unlimited
&& (($includedVariantId !== null && $variant->id === $includedVariantId) || ($variant->inventory?->availableStock() ?? 0) > 0)
|| ($variant->isSellable() && (
$this->inventory_policy === InventoryPolicy::Unlimited
|| ($variant->inventory?->availableStock() ?? 0) > 0
))))
->values(); ->values();
} }

Some files were not shown because too many files have changed in this diff Show More