Compare commits
7 Commits
tennant/on
...
feature/al
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c646f3560 | |||
| 98bfc0d5e9 | |||
| d9635d47ba | |||
| e27f7bb166 | |||
| 2323994f30 | |||
| a73b628bb4 | |||
| 2fd6851b40 |
@@ -8,7 +8,6 @@ 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
|
||||
FRONTEND_URLS=http://localhost:4200
|
||||
|
||||
@@ -32,7 +31,6 @@ AUTH_LOGIN_ATTEMPT_WINDOW_MINUTES=30
|
||||
AUTH_LOGIN_LOCK_MINUTES=15
|
||||
AUTH_LOGIN_RATE_LIMIT_PER_MINUTE=10
|
||||
AUTH_LOGIN_IP_RATE_LIMIT_PER_MINUTE=30
|
||||
AUTH_PASSWORD_RESET_EXPIRATION_MINUTES=60
|
||||
|
||||
LOG_CHANNEL=daily
|
||||
LOG_STACK=single
|
||||
@@ -43,9 +41,6 @@ TELEPAGOS_LOG_LEVEL=info
|
||||
TELEPAGOS_LOG_DAYS=30
|
||||
COMMANDS_LOG_LEVEL=info
|
||||
COMMANDS_LOG_DAYS=30
|
||||
EMAILS_LOG_LEVEL=info
|
||||
EMAILS_LOG_DAYS=30
|
||||
EMAIL_DELIVERY_LEASE_SECONDS=300
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
|
||||
54
AGENTS.md
54
AGENTS.md
@@ -1,29 +1,19 @@
|
||||
# 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
|
||||
|
||||
This project uses a domain-oriented structure grouped by business area under
|
||||
`app/Domains`. Cross-cutting modules live under `app/Shared`.
|
||||
This project uses a domain-oriented structure under `app/Domains`.
|
||||
|
||||
Business domains must live in:
|
||||
Each domain must live in:
|
||||
|
||||
```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:
|
||||
|
||||
```text
|
||||
app/Domains/<Area>/<DomainName>/
|
||||
app/Domains/<DomainName>/
|
||||
├── Models/
|
||||
├── Controllers/
|
||||
├── Requests/
|
||||
@@ -38,28 +28,22 @@ app/Domains/<Area>/<DomainName>/
|
||||
|
||||
- Root API routes live in `routes/api.php`.
|
||||
- `routes/api.php` should act as an aggregator only.
|
||||
- Each business domain defines its own routes in
|
||||
`app/Domains/<Area>/<DomainName>/routes/api.php`.
|
||||
- Shared modules that expose routes define them in
|
||||
`app/Shared/<ModuleName>/routes/api.php`.
|
||||
- Each domain defines its own routes in `app/Domains/<DomainName>/routes/api.php`.
|
||||
|
||||
## Namespaces
|
||||
|
||||
Use this namespace pattern:
|
||||
|
||||
```php
|
||||
App\Domains\<Area>\<DomainName>\Models
|
||||
App\Domains\<Area>\<DomainName>\Controllers
|
||||
App\Domains\<Area>\<DomainName>\Requests
|
||||
App\Domains\<Area>\<DomainName>\Resources
|
||||
App\Domains\<Area>\<DomainName>\Services
|
||||
App\Domains\<Area>\<DomainName>\Policies
|
||||
App\Domains\<Area>\<DomainName>\Exceptions
|
||||
App\Domains\<DomainName>\Models
|
||||
App\Domains\<DomainName>\Controllers
|
||||
App\Domains\<DomainName>\Requests
|
||||
App\Domains\<DomainName>\Resources
|
||||
App\Domains\<DomainName>\Services
|
||||
App\Domains\<DomainName>\Policies
|
||||
App\Domains\<DomainName>\Exceptions
|
||||
```
|
||||
|
||||
For cross-cutting modules, use `App\Shared\<ModuleName>\...`. Reusable enums
|
||||
and rules live directly under `App\Shared`.
|
||||
|
||||
## Responsibilities
|
||||
|
||||
- `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.
|
||||
- Route files should be named `api.php`.
|
||||
|
||||
## Current Areas and Modules
|
||||
## Current Domains
|
||||
|
||||
- `Core`: `Administrator`, `Auth`, `Authorization`, `Bootstrap`, `Client`,
|
||||
`Menu`, `Staff`, `Tenant`.
|
||||
- `Commerce`: `Cart`, `Catalog`, `Purchase`, `Sale`.
|
||||
- `Ticketing`: `Desfile`, `Event`, `FiestaFutbolInfantil`, `Ticket`.
|
||||
- `Shared`: `Attachable`, `Forms`, `Integration`, `Logging`, `MailTest`,
|
||||
`Notification`, `StorageTest`, plus shared enums and rules.
|
||||
- `Tenant`
|
||||
- `Product`
|
||||
|
||||
## Notes For Future Changes
|
||||
|
||||
- New features should be added inside the corresponding domain first.
|
||||
- Add new business features to the appropriate area under `app/Domains`.
|
||||
- Keep generic reusable code in `app/Shared`; do not use it as a catch-all for
|
||||
business features.
|
||||
- If a concern does not belong to an existing domain, create a new domain under `app/Domains`.
|
||||
- Avoid placing new business code in `app/Http/Controllers` or `app/Models`.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Shared\Attachable\Enums;
|
||||
namespace App\Domains\Attachable\Enums;
|
||||
|
||||
enum AttachmentType: string
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Shared\Attachable\Exceptions;
|
||||
namespace App\Domains\Attachable\Exceptions;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?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\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Shared\Attachable\Models;
|
||||
namespace App\Domains\Attachable\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Shared\Attachable\Services;
|
||||
namespace App\Domains\Attachable\Services;
|
||||
|
||||
use App\Shared\Attachable\Enums\AttachmentType;
|
||||
use App\Shared\Attachable\Exceptions\AttachmentStorageException;
|
||||
use App\Shared\Attachable\Models\Attachment;
|
||||
use App\Shared\Attachable\Models\AttachmentCrop;
|
||||
use App\Domains\Attachable\Enums\AttachmentType;
|
||||
use App\Domains\Attachable\Exceptions\AttachmentStorageException;
|
||||
use App\Domains\Attachable\Models\Attachment;
|
||||
use App\Domains\Attachable\Models\AttachmentCrop;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Controllers;
|
||||
namespace App\Domains\Auth\Controllers;
|
||||
|
||||
use App\Domains\Core\Auth\Requests\AdminAppLoginRequest;
|
||||
use App\Domains\Core\Auth\Resources\UserResource;
|
||||
use App\Domains\Core\Auth\Services\PasswordLoginService;
|
||||
use App\Domains\Auth\Requests\AdminAppLoginRequest;
|
||||
use App\Domains\Auth\Resources\UserResource;
|
||||
use App\Domains\Auth\Services\PasswordLoginService;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Controllers;
|
||||
namespace App\Domains\Auth\Controllers;
|
||||
|
||||
use App\Domains\Core\Auth\Models\User;
|
||||
use App\Domains\Core\Auth\Resources\AdminAppMeResource;
|
||||
use App\Domains\Core\Auth\Services\AdminAppContextService;
|
||||
use App\Domains\Auth\Models\User;
|
||||
use App\Domains\Auth\Resources\AdminAppMeResource;
|
||||
use App\Domains\Auth\Services\AdminAppContextService;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Controllers;
|
||||
namespace App\Domains\Auth\Controllers;
|
||||
|
||||
use App\Domains\Core\Auth\Models\ResetPasswordAttempt;
|
||||
use App\Domains\Core\Auth\Requests\AdminAppCreateResetPasswordAttemptRequest;
|
||||
use App\Domains\Core\Auth\Services\ResetPasswordAttemptService;
|
||||
use App\Domains\Auth\Models\ResetPasswordAttempt;
|
||||
use App\Domains\Auth\Requests\AdminAppCreateResetPasswordAttemptRequest;
|
||||
use App\Domains\Auth\Services\ResetPasswordAttemptService;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Controllers;
|
||||
namespace App\Domains\Auth\Controllers;
|
||||
|
||||
use App\Domains\Core\Auth\Models\ResetPasswordAttempt;
|
||||
use App\Domains\Core\Auth\Requests\CreateResetPasswordAttemptRequest;
|
||||
use App\Domains\Core\Auth\Services\ResetPasswordAttemptService;
|
||||
use App\Domains\Auth\Models\ResetPasswordAttempt;
|
||||
use App\Domains\Auth\Requests\CreateResetPasswordAttemptRequest;
|
||||
use App\Domains\Auth\Services\ResetPasswordAttemptService;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Controllers;
|
||||
namespace App\Domains\Auth\Controllers;
|
||||
|
||||
use App\Domains\Core\Auth\Models\ResetPasswordAttempt;
|
||||
use App\Domains\Core\Auth\Requests\ScannerCreateResetPasswordAttemptRequest;
|
||||
use App\Domains\Core\Auth\Services\ResetPasswordAttemptService;
|
||||
use App\Domains\Auth\Models\ResetPasswordAttempt;
|
||||
use App\Domains\Auth\Requests\ScannerCreateResetPasswordAttemptRequest;
|
||||
use App\Domains\Auth\Services\ResetPasswordAttemptService;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?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 Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Controllers;
|
||||
namespace App\Domains\Auth\Controllers;
|
||||
|
||||
use App\Domains\Core\Auth\Requests\GoogleTokenExchangeRequest;
|
||||
use App\Domains\Core\Auth\Resources\UserResource;
|
||||
use App\Domains\Core\Auth\Services\GoogleAuthService;
|
||||
use App\Domains\Commerce\Cart\Services\GuestCartMergeService;
|
||||
use App\Domains\Auth\Requests\GoogleTokenExchangeRequest;
|
||||
use App\Domains\Auth\Resources\UserResource;
|
||||
use App\Domains\Auth\Services\GoogleAuthService;
|
||||
use App\Domains\Cart\Services\GuestCartMergeService;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Cookie;
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Controllers;
|
||||
namespace App\Domains\Auth\Controllers;
|
||||
|
||||
use App\Domains\Core\Auth\Requests\LoginUserRequest;
|
||||
use App\Domains\Core\Auth\Resources\UserResource;
|
||||
use App\Domains\Core\Auth\Services\PasswordLoginService;
|
||||
use App\Domains\Commerce\Cart\Services\GuestCartMergeService;
|
||||
use App\Domains\Auth\Requests\LoginUserRequest;
|
||||
use App\Domains\Auth\Resources\UserResource;
|
||||
use App\Domains\Auth\Services\PasswordLoginService;
|
||||
use App\Domains\Cart\Services\GuestCartMergeService;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Cookie;
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Controllers;
|
||||
namespace App\Domains\Auth\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
@@ -1,8 +1,8 @@
|
||||
<?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\Request;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Controllers;
|
||||
namespace App\Domains\Auth\Controllers;
|
||||
|
||||
use App\Domains\Core\Auth\Requests\RegisterUserRequest;
|
||||
use App\Domains\Core\Auth\Resources\UserResource;
|
||||
use App\Domains\Core\Auth\Services\RegisterUserService;
|
||||
use App\Domains\Auth\Requests\RegisterUserRequest;
|
||||
use App\Domains\Auth\Resources\UserResource;
|
||||
use App\Domains\Auth\Services\RegisterUserService;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Controllers;
|
||||
namespace App\Domains\Auth\Controllers;
|
||||
|
||||
use App\Domains\Core\Auth\Models\ResetPasswordAttempt;
|
||||
use App\Domains\Core\Auth\Requests\ResetPasswordRequest;
|
||||
use App\Domains\Core\Auth\Services\ResetPasswordAttemptService;
|
||||
use App\Domains\Core\Authorization\Enums\RoleCode;
|
||||
use App\Domains\Auth\Models\ResetPasswordAttempt;
|
||||
use App\Domains\Auth\Requests\ResetPasswordRequest;
|
||||
use App\Domains\Auth\Services\ResetPasswordAttemptService;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
@@ -27,7 +26,6 @@ class ResetPasswordController extends Controller
|
||||
$data['email'],
|
||||
$data['codigo'],
|
||||
$data['password'],
|
||||
RoleCode::from($request->route('reset_role', RoleCode::User->value)),
|
||||
)) {
|
||||
throw ValidationException::withMessages([
|
||||
'codigo' => __('api.auth.password_reset_invalid'),
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Controllers;
|
||||
namespace App\Domains\Auth\Controllers;
|
||||
|
||||
use App\Domains\Core\Auth\Requests\ScannerLoginRequest;
|
||||
use App\Domains\Core\Auth\Resources\UserResource;
|
||||
use App\Domains\Core\Auth\Services\PasswordLoginService;
|
||||
use App\Domains\Auth\Requests\ScannerLoginRequest;
|
||||
use App\Domains\Auth\Resources\UserResource;
|
||||
use App\Domains\Auth\Services\PasswordLoginService;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Controllers;
|
||||
namespace App\Domains\Auth\Controllers;
|
||||
|
||||
use App\Domains\Core\Auth\Models\User;
|
||||
use App\Domains\Core\Auth\Resources\ScannerMeResource;
|
||||
use App\Domains\Core\Auth\Services\ScannerContextService;
|
||||
use App\Domains\Auth\Models\User;
|
||||
use App\Domains\Auth\Resources\ScannerMeResource;
|
||||
use App\Domains\Auth\Services\ScannerContextService;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Controllers;
|
||||
namespace App\Domains\Auth\Controllers;
|
||||
|
||||
use App\Domains\Core\Auth\Requests\UpdateProfileRequest;
|
||||
use App\Domains\Core\Auth\Resources\UserResource;
|
||||
use App\Domains\Core\Auth\Services\ProfileService;
|
||||
use App\Domains\Auth\Requests\UpdateProfileRequest;
|
||||
use App\Domains\Auth\Resources\UserResource;
|
||||
use App\Domains\Auth\Services\ProfileService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class UpdateProfileController
|
||||
@@ -1,11 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Controllers;
|
||||
namespace App\Domains\Auth\Controllers;
|
||||
|
||||
use App\Domains\Core\Auth\Models\ResetPasswordAttempt;
|
||||
use App\Domains\Core\Auth\Requests\ValidateResetPasswordAttemptRequest;
|
||||
use App\Domains\Core\Auth\Services\ResetPasswordAttemptService;
|
||||
use App\Domains\Core\Authorization\Enums\RoleCode;
|
||||
use App\Domains\Auth\Models\ResetPasswordAttempt;
|
||||
use App\Domains\Auth\Requests\ValidateResetPasswordAttemptRequest;
|
||||
use App\Domains\Auth\Services\ResetPasswordAttemptService;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
@@ -23,19 +22,10 @@ class ValidateResetPasswordAttemptController extends Controller
|
||||
{
|
||||
$data = $request->validated();
|
||||
|
||||
$result = $this->resetPasswordAttemptService->validateCode(
|
||||
if (! $this->resetPasswordAttemptService->validateCode(
|
||||
$data['email'],
|
||||
$data['codigo'],
|
||||
RoleCode::from($request->route('reset_role', RoleCode::User->value)),
|
||||
);
|
||||
|
||||
if ($result === ResetPasswordAttemptService::CODE_EXPIRED) {
|
||||
throw ValidationException::withMessages([
|
||||
'codigo' => __('api.auth.reset_code_expired'),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($result !== ResetPasswordAttemptService::CODE_VALID) {
|
||||
)) {
|
||||
throw ValidationException::withMessages([
|
||||
'codigo' => __('api.auth.reset_code_invalid'),
|
||||
]);
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Exceptions;
|
||||
namespace App\Domains\Auth\Exceptions;
|
||||
|
||||
use Carbon\CarbonImmutable;
|
||||
use RuntimeException;
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Models;
|
||||
namespace App\Domains\Auth\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -1,13 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Models;
|
||||
namespace App\Domains\Auth\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
#[Fillable(['user_id', 'codigo', 'reason', 'status', 'expires_at'])]
|
||||
#[Fillable(['user_id', 'codigo', 'reason', 'status'])]
|
||||
#[Hidden(['codigo'])]
|
||||
class ResetPasswordAttempt extends Model
|
||||
{
|
||||
@@ -17,8 +17,6 @@ class ResetPasswordAttempt extends Model
|
||||
|
||||
public const REASON_STAFF_CREATED = 'staff_created';
|
||||
|
||||
public const REASON_ADMINISTRATOR_CREATED = 'administrator_created';
|
||||
|
||||
public const STATUS_PENDING = 'pending';
|
||||
|
||||
public const STATUS_VALIDATED = 'validated';
|
||||
@@ -33,7 +31,6 @@ class ResetPasswordAttempt extends Model
|
||||
{
|
||||
return [
|
||||
'user_id' => 'integer',
|
||||
'expires_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Models;
|
||||
namespace App\Domains\Auth\Models;
|
||||
|
||||
use App\Domains\Core\Authorization\Enums\RoleCode;
|
||||
use App\Domains\Core\Authorization\Models\Role;
|
||||
use App\Domains\Commerce\Catalog\Models\Category;
|
||||
use App\Domains\Ticketing\Event\Models\EventDateChangeView;
|
||||
use App\Domains\Core\Tenant\Models\Tenant;
|
||||
use App\Domains\Ticketing\Ticket\Models\ScanAttempt;
|
||||
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;
|
||||
@@ -15,17 +13,16 @@ 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\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
|
||||
#[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
|
||||
{
|
||||
/** @use HasFactory<UserFactory> */
|
||||
use HasApiTokens, HasFactory, Notifiable, SoftDeletes;
|
||||
use HasApiTokens, HasFactory, Notifiable;
|
||||
|
||||
protected $attributes = [
|
||||
'rol_codigo' => RoleCode::User->value,
|
||||
@@ -48,18 +45,6 @@ class User extends Authenticatable
|
||||
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>
|
||||
*/
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Requests;
|
||||
namespace App\Domains\Auth\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Requests;
|
||||
namespace App\Domains\Auth\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Requests;
|
||||
namespace App\Domains\Auth\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Requests;
|
||||
namespace App\Domains\Auth\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Requests;
|
||||
namespace App\Domains\Auth\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -1,8 +1,7 @@
|
||||
<?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\Validation\Rule;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
@@ -14,26 +13,15 @@ class RegisterUserRequest extends FormRequest
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
if (is_string($this->input('email'))) {
|
||||
$this->merge(['email' => mb_strtolower(trim($this->input('email')))]);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'tenant_codigo' => ['nullable', 'string', Rule::exists('tenants', 'codigo')],
|
||||
'nombre_apellido' => ['required', 'string', 'max:255'],
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
'email',
|
||||
'max:255',
|
||||
Rule::unique('users', 'active_email')->where('rol_codigo', RoleCode::User->value)->whereNull('deleted_at'),
|
||||
],
|
||||
'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users', 'email')],
|
||||
'password' => ['required', 'string', 'confirmed', Password::min(8)->mixedCase()->symbols()],
|
||||
'dni' => ['nullable', 'string', 'max:255'],
|
||||
'telefono' => ['nullable', 'string', 'max:255'],
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Requests;
|
||||
namespace App\Domains\Auth\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Requests;
|
||||
namespace App\Domains\Auth\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Requests;
|
||||
namespace App\Domains\Auth\Requests;
|
||||
|
||||
class ScannerLoginRequest extends AdminAppLoginRequest {}
|
||||
@@ -1,10 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Requests;
|
||||
namespace App\Domains\Auth\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class UpdateProfileRequest extends FormRequest
|
||||
{
|
||||
@@ -13,13 +12,6 @@ class UpdateProfileRequest extends FormRequest
|
||||
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
|
||||
{
|
||||
return [
|
||||
@@ -27,13 +19,11 @@ class UpdateProfileRequest extends FormRequest
|
||||
'email' => [
|
||||
'required',
|
||||
'email',
|
||||
Rule::unique('users', 'active_email')->where('rol_codigo', $this->user()->rol_codigo)
|
||||
->whereNull('deleted_at')
|
||||
->ignore($this->user()->id),
|
||||
Rule::unique('users', 'email')->ignore($this->user()->id),
|
||||
],
|
||||
'dni' => ['nullable', 'string', 'regex:/^[0-9]{7,8}$/'],
|
||||
'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()],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Requests;
|
||||
namespace App\Domains\Auth\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Resources;
|
||||
namespace App\Domains\Auth\Resources;
|
||||
|
||||
use App\Domains\Core\Auth\Models\User;
|
||||
use App\Domains\Core\Tenant\Resources\TenantResource;
|
||||
use App\Domains\Auth\Models\User;
|
||||
use App\Domains\Tenant\Resources\TenantResource;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Resources;
|
||||
namespace App\Domains\Auth\Resources;
|
||||
|
||||
use App\Domains\Core\Auth\Models\User;
|
||||
use App\Domains\Core\Tenant\Resources\TenantResource;
|
||||
use App\Domains\Auth\Models\User;
|
||||
use App\Domains\Tenant\Resources\TenantResource;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?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\Resources\Json\JsonResource;
|
||||
|
||||
@@ -24,11 +24,6 @@ class UserResource extends JsonResource
|
||||
'telefono' => $this->telefono,
|
||||
'rol_codigo' => $this->rol_codigo,
|
||||
'tenant_codigo' => $this->tenant_codigo,
|
||||
'categories' => $this->whenLoaded('scanCategories', fn () => $this->scanCategories
|
||||
->map(fn ($category) => [
|
||||
'id' => $category->id,
|
||||
'nombre' => $category->nombre,
|
||||
])->values()),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Services;
|
||||
namespace App\Domains\Auth\Services;
|
||||
|
||||
use App\Domains\Core\Auth\Models\User;
|
||||
use App\Domains\Core\Authorization\Enums\RoleCode;
|
||||
use App\Domains\Auth\Models\User;
|
||||
use App\Domains\Authorization\Enums\RoleCode;
|
||||
|
||||
class AdminAppContextService
|
||||
{
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Services;
|
||||
namespace App\Domains\Auth\Services;
|
||||
|
||||
use App\Domains\Core\Auth\Models\User;
|
||||
use App\Domains\Core\Authorization\Enums\RoleCode;
|
||||
use App\Domains\Auth\Models\User;
|
||||
use App\Domains\Authorization\Enums\RoleCode;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class AdminCredentialVerifier
|
||||
@@ -11,7 +11,7 @@ class AdminCredentialVerifier
|
||||
public function verify(string $email, string $password): bool
|
||||
{
|
||||
$admin = User::query()
|
||||
->where('active_email', mb_strtolower(trim($email)))
|
||||
->where('email', mb_strtolower(trim($email)))
|
||||
->where('rol_codigo', RoleCode::Admin->value)
|
||||
->first();
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Services;
|
||||
namespace App\Domains\Auth\Services;
|
||||
|
||||
use App\Domains\Core\Auth\Models\User;
|
||||
use App\Domains\Core\Authorization\Enums\RoleCode;
|
||||
use App\Shared\Notification\Events\UserRegistered;
|
||||
use App\Domains\Core\Tenant\Models\Tenant;
|
||||
use App\Domains\Core\Tenant\Support\TenantDomainNormalizer;
|
||||
use App\Domains\Auth\Models\User;
|
||||
use App\Domains\Notification\Events\UserRegistered;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use App\Domains\Tenant\Support\TenantDomainNormalizer;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
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) {
|
||||
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) {
|
||||
$user->forceFill(['google_id' => $googleId])->save();
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Services;
|
||||
namespace App\Domains\Auth\Services;
|
||||
|
||||
use App\Domains\Core\Auth\Exceptions\AccountLockedException;
|
||||
use App\Domains\Core\Auth\Models\LoginAttempt;
|
||||
use App\Domains\Core\Auth\Models\ResetPasswordAttempt;
|
||||
use App\Domains\Core\Auth\Models\User;
|
||||
use App\Domains\Core\Authorization\Enums\PermissionCode;
|
||||
use App\Domains\Core\Authorization\Enums\RoleCode;
|
||||
use App\Shared\Notification\Events\PasswordResetRequested;
|
||||
use App\Domains\Auth\Exceptions\AccountLockedException;
|
||||
use App\Domains\Auth\Models\LoginAttempt;
|
||||
use App\Domains\Auth\Models\ResetPasswordAttempt;
|
||||
use App\Domains\Auth\Models\User;
|
||||
use App\Domains\Authorization\Enums\PermissionCode;
|
||||
use App\Domains\Authorization\Enums\RoleCode;
|
||||
use App\Domains\Notification\Events\PasswordResetRequested;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
@@ -85,7 +85,7 @@ class PasswordLoginService
|
||||
null,
|
||||
$ipAddress,
|
||||
$userAgent,
|
||||
RoleCode::Scanner,
|
||||
null,
|
||||
true,
|
||||
PermissionCode::ScanTickets->value,
|
||||
PasswordResetRequested::CHANNEL_SCANNER,
|
||||
@@ -120,7 +120,7 @@ class PasswordLoginService
|
||||
$passwordResetChannel,
|
||||
): array {
|
||||
$user = User::query()
|
||||
->where('active_email', $normalizedEmail)
|
||||
->where('email', $normalizedEmail)
|
||||
->when(
|
||||
$requiredRole !== null,
|
||||
fn ($query) => $query->where('rol_codigo', $requiredRole->value),
|
||||
@@ -1,8 +1,8 @@
|
||||
<?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;
|
||||
|
||||
class ProfileService
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Services;
|
||||
namespace App\Domains\Auth\Services;
|
||||
|
||||
use App\Domains\Core\Auth\Models\User;
|
||||
use App\Shared\Notification\Events\UserRegistered;
|
||||
use App\Domains\Auth\Models\User;
|
||||
use App\Domains\Notification\Events\UserRegistered;
|
||||
|
||||
class RegisterUserService
|
||||
{
|
||||
@@ -1,23 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Auth\Services;
|
||||
namespace App\Domains\Auth\Services;
|
||||
|
||||
use App\Domains\Core\Auth\Models\ResetPasswordAttempt;
|
||||
use App\Domains\Core\Auth\Models\User;
|
||||
use App\Domains\Core\Authorization\Enums\RoleCode;
|
||||
use App\Shared\Notification\Events\PasswordResetRequested;
|
||||
use App\Domains\Auth\Models\ResetPasswordAttempt;
|
||||
use App\Domains\Auth\Models\User;
|
||||
use App\Domains\Authorization\Enums\RoleCode;
|
||||
use App\Domains\Notification\Events\PasswordResetRequested;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Throwable;
|
||||
|
||||
class ResetPasswordAttemptService
|
||||
{
|
||||
public const CODE_VALID = 'valid';
|
||||
|
||||
public const CODE_INVALID = 'invalid';
|
||||
|
||||
public const CODE_EXPIRED = 'expired';
|
||||
|
||||
public function createForEmail(
|
||||
string $email,
|
||||
string $tenantCode,
|
||||
@@ -28,8 +22,7 @@ class ResetPasswordAttemptService
|
||||
try {
|
||||
$attemptId = DB::transaction(function () use ($email, $emailFingerprint, $reason): ?int {
|
||||
$user = User::query()
|
||||
->where('active_email', mb_strtolower(trim($email)))
|
||||
->where('rol_codigo', RoleCode::User->value)
|
||||
->where('email', $email)
|
||||
->lockForUpdate()
|
||||
->first();
|
||||
|
||||
@@ -66,7 +59,7 @@ class ResetPasswordAttemptService
|
||||
try {
|
||||
$result = DB::transaction(function () use ($email, $emailFingerprint, $reason): ?array {
|
||||
$user = User::query()
|
||||
->where('active_email', mb_strtolower(trim($email)))
|
||||
->where('email', $email)
|
||||
->where('rol_codigo', RoleCode::AdminApp->value)
|
||||
->whereNotNull('tenant_codigo')
|
||||
->lockForUpdate()
|
||||
@@ -114,7 +107,7 @@ class ResetPasswordAttemptService
|
||||
try {
|
||||
$result = DB::transaction(function () use ($email, $emailFingerprint, $reason): ?array {
|
||||
$user = User::query()
|
||||
->where('active_email', mb_strtolower(trim($email)))
|
||||
->where('email', $email)
|
||||
->where('rol_codigo', RoleCode::Scanner->value)
|
||||
->whereNotNull('tenant_codigo')
|
||||
->lockForUpdate()
|
||||
@@ -153,15 +146,14 @@ class ResetPasswordAttemptService
|
||||
);
|
||||
}
|
||||
|
||||
public function validateCode(string $email, string $code, RoleCode $role = RoleCode::User): string
|
||||
public function validateCode(string $email, string $code): bool
|
||||
{
|
||||
$emailFingerprint = $this->emailFingerprint($email);
|
||||
|
||||
try {
|
||||
return DB::transaction(function () use ($email, $code, $emailFingerprint, $role): string {
|
||||
return DB::transaction(function () use ($email, $code, $emailFingerprint): bool {
|
||||
$user = User::query()
|
||||
->where('active_email', mb_strtolower(trim($email)))
|
||||
->where('rol_codigo', $role->value)
|
||||
->where('email', $email)
|
||||
->lockForUpdate()
|
||||
->first();
|
||||
|
||||
@@ -177,27 +169,14 @@ class ResetPasswordAttemptService
|
||||
'email_fingerprint' => $emailFingerprint,
|
||||
]);
|
||||
|
||||
return self::CODE_INVALID;
|
||||
}
|
||||
|
||||
if ($attempt->expires_at?->isPast()) {
|
||||
$attempt->update([
|
||||
'status' => ResetPasswordAttempt::STATUS_EXPIRED,
|
||||
]);
|
||||
|
||||
Log::info('Password reset code validation failed: attempt expired.', [
|
||||
'email_fingerprint' => $emailFingerprint,
|
||||
'attempt_id' => $attempt->getKey(),
|
||||
]);
|
||||
|
||||
return self::CODE_EXPIRED;
|
||||
return false;
|
||||
}
|
||||
|
||||
$attempt->update([
|
||||
'status' => ResetPasswordAttempt::STATUS_VALIDATED,
|
||||
]);
|
||||
|
||||
return self::CODE_VALID;
|
||||
return true;
|
||||
});
|
||||
} catch (Throwable $exception) {
|
||||
Log::error('Failed to validate password reset code.', [
|
||||
@@ -209,15 +188,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);
|
||||
|
||||
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()
|
||||
->where('active_email', mb_strtolower(trim($email)))
|
||||
->where('rol_codigo', $role->value)
|
||||
->where('email', $email)
|
||||
->lockForUpdate()
|
||||
->first();
|
||||
|
||||
@@ -236,19 +214,6 @@ class ResetPasswordAttemptService
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($attempt->expires_at?->isPast()) {
|
||||
$attempt->update([
|
||||
'status' => ResetPasswordAttempt::STATUS_EXPIRED,
|
||||
]);
|
||||
|
||||
Log::info('Password reset failed: attempt expired.', [
|
||||
'email_fingerprint' => $emailFingerprint,
|
||||
'attempt_id' => $attempt->getKey(),
|
||||
]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$user->password = $password;
|
||||
$user->failed_login_attempts = 0;
|
||||
$user->last_failed_login_at = null;
|
||||
@@ -305,7 +270,6 @@ class ResetPasswordAttemptService
|
||||
'codigo' => $this->generateCode(),
|
||||
'reason' => $reason,
|
||||
'status' => ResetPasswordAttempt::STATUS_PENDING,
|
||||
'expires_at' => now()->addMinutes((int) config('auth.passwords.users.expire')),
|
||||
]);
|
||||
|
||||
return $attempt->getKey();
|
||||
@@ -1,8 +1,8 @@
|
||||
<?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
|
||||
{
|
||||
@@ -19,16 +19,6 @@ class ScannerContextService
|
||||
|
||||
$user->setRelation('tenant', $tenant);
|
||||
|
||||
if ($tenant->requiresScannerCategoryValidation()) {
|
||||
$categories = $user->scanCategories()
|
||||
->orderBy('nombre')
|
||||
->get();
|
||||
|
||||
if ($categories->isNotEmpty()) {
|
||||
$user->setRelation('scanCategories', $categories);
|
||||
}
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Core\Auth\Controllers\AdminAppLoginController;
|
||||
use App\Domains\Core\Auth\Controllers\AdminAppMeController;
|
||||
use App\Domains\Core\Auth\Controllers\CreateAdminAppResetPasswordAttemptController;
|
||||
use App\Domains\Core\Auth\Controllers\ResetPasswordController;
|
||||
use App\Domains\Core\Auth\Controllers\ValidateResetPasswordAttemptController;
|
||||
use App\Domains\Auth\Controllers\AdminAppLoginController;
|
||||
use App\Domains\Auth\Controllers\AdminAppMeController;
|
||||
use App\Domains\Auth\Controllers\CreateAdminAppResetPasswordAttemptController;
|
||||
use App\Domains\Auth\Controllers\ResetPasswordController;
|
||||
use App\Domains\Auth\Controllers\ValidateResetPasswordAttemptController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
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)
|
||||
->middleware('throttle:5,1');
|
||||
Route::post('password/reset-attempts/validate', ValidateResetPasswordAttemptController::class)
|
||||
->defaults('reset_role', 'adminapp')
|
||||
->middleware('throttle:10,1');
|
||||
Route::post('password/reset', ResetPasswordController::class)
|
||||
->defaults('reset_role', 'adminapp')
|
||||
->middleware('throttle:5,1');
|
||||
Route::middleware(['auth:sanctum', 'adminapp.tenant'])
|
||||
->get('me', AdminAppMeController::class);
|
||||
@@ -1,14 +1,14 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Core\Auth\Controllers\CreateResetPasswordAttemptController;
|
||||
use App\Domains\Core\Auth\Controllers\GoogleTokenExchangeController;
|
||||
use App\Domains\Core\Auth\Controllers\LoginController;
|
||||
use App\Domains\Core\Auth\Controllers\LogoutController;
|
||||
use App\Domains\Core\Auth\Controllers\MeController;
|
||||
use App\Domains\Core\Auth\Controllers\RegisterController;
|
||||
use App\Domains\Core\Auth\Controllers\ResetPasswordController;
|
||||
use App\Domains\Core\Auth\Controllers\UpdateProfileController;
|
||||
use App\Domains\Core\Auth\Controllers\ValidateResetPasswordAttemptController;
|
||||
use App\Domains\Auth\Controllers\CreateResetPasswordAttemptController;
|
||||
use App\Domains\Auth\Controllers\GoogleTokenExchangeController;
|
||||
use App\Domains\Auth\Controllers\LoginController;
|
||||
use App\Domains\Auth\Controllers\LogoutController;
|
||||
use App\Domains\Auth\Controllers\MeController;
|
||||
use App\Domains\Auth\Controllers\RegisterController;
|
||||
use App\Domains\Auth\Controllers\ResetPasswordController;
|
||||
use App\Domains\Auth\Controllers\UpdateProfileController;
|
||||
use App\Domains\Auth\Controllers\ValidateResetPasswordAttemptController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::post('/register', RegisterController::class);
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Core\Auth\Controllers\CreateScannerResetPasswordAttemptController;
|
||||
use App\Domains\Core\Auth\Controllers\ResetPasswordController;
|
||||
use App\Domains\Core\Auth\Controllers\ScannerLoginController;
|
||||
use App\Domains\Core\Auth\Controllers\ScannerMeController;
|
||||
use App\Domains\Core\Auth\Controllers\ValidateResetPasswordAttemptController;
|
||||
use App\Domains\Auth\Controllers\CreateScannerResetPasswordAttemptController;
|
||||
use App\Domains\Auth\Controllers\ResetPasswordController;
|
||||
use App\Domains\Auth\Controllers\ScannerLoginController;
|
||||
use App\Domains\Auth\Controllers\ScannerMeController;
|
||||
use App\Domains\Auth\Controllers\ValidateResetPasswordAttemptController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
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)
|
||||
->middleware('throttle:5,1');
|
||||
Route::post('password/reset-attempts/validate', ValidateResetPasswordAttemptController::class)
|
||||
->defaults('reset_role', 'scanner')
|
||||
->middleware('throttle:10,1');
|
||||
Route::post('password/reset', ResetPasswordController::class)
|
||||
->defaults('reset_role', 'scanner')
|
||||
->middleware('throttle:5,1');
|
||||
Route::middleware(['auth:sanctum', 'scanner.tenant'])
|
||||
->get('me', ScannerMeController::class);
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Authorization\Enums;
|
||||
namespace App\Domains\Authorization\Enums;
|
||||
|
||||
enum PermissionCode: string
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Authorization\Enums;
|
||||
namespace App\Domains\Authorization\Enums;
|
||||
|
||||
enum RoleCode: string
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Authorization\Models;
|
||||
namespace App\Domains\Authorization\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Authorization\Models;
|
||||
namespace App\Domains\Authorization\Models;
|
||||
|
||||
use App\Domains\Core\Auth\Models\User;
|
||||
use App\Domains\Core\Menu\Models\Menu;
|
||||
use App\Domains\Core\Menu\Models\MenuRole;
|
||||
use App\Domains\Auth\Models\User;
|
||||
use App\Domains\Menu\Models\Menu;
|
||||
use App\Domains\Menu\Models\MenuRole;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Authorization\Models;
|
||||
namespace App\Domains\Authorization\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Bootstrap\Controllers;
|
||||
namespace App\Domains\Bootstrap\Controllers;
|
||||
|
||||
use App\Domains\Core\Bootstrap\Requests\AdminAppBootstrapRequest;
|
||||
use App\Domains\Core\Bootstrap\Resources\AdminAppBootstrapResource;
|
||||
use App\Domains\Core\Bootstrap\Services\AdminAppBootstrapService;
|
||||
use App\Domains\Bootstrap\Requests\AdminAppBootstrapRequest;
|
||||
use App\Domains\Bootstrap\Resources\AdminAppBootstrapResource;
|
||||
use App\Domains\Bootstrap\Services\AdminAppBootstrapService;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class AdminAppBootstrapController extends Controller
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Bootstrap\Controllers;
|
||||
namespace App\Domains\Bootstrap\Controllers;
|
||||
|
||||
use App\Domains\Core\Bootstrap\Requests\ScannerBootstrapRequest;
|
||||
use App\Domains\Core\Bootstrap\Resources\ScannerBootstrapResource;
|
||||
use App\Domains\Core\Bootstrap\Services\ScannerBootstrapService;
|
||||
use App\Domains\Bootstrap\Requests\ScannerBootstrapRequest;
|
||||
use App\Domains\Bootstrap\Resources\ScannerBootstrapResource;
|
||||
use App\Domains\Bootstrap\Services\ScannerBootstrapService;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class ScannerBootstrapController extends Controller
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Bootstrap\Controllers;
|
||||
namespace App\Domains\Bootstrap\Controllers;
|
||||
|
||||
use App\Domains\Core\Bootstrap\Requests\TenantBootstrapRequest;
|
||||
use App\Domains\Core\Bootstrap\Services\TenantBootstrapService;
|
||||
use App\Domains\Core\Tenant\Resources\TenantResource;
|
||||
use App\Domains\Bootstrap\Requests\TenantBootstrapRequest;
|
||||
use App\Domains\Bootstrap\Services\TenantBootstrapService;
|
||||
use App\Domains\Tenant\Resources\TenantResource;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class TenantBootstrapController extends Controller
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Bootstrap\Requests;
|
||||
namespace App\Domains\Bootstrap\Requests;
|
||||
|
||||
class AdminAppBootstrapRequest extends TenantBootstrapRequest {}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Bootstrap\Requests;
|
||||
namespace App\Domains\Bootstrap\Requests;
|
||||
|
||||
class ScannerBootstrapRequest extends TenantBootstrapRequest {}
|
||||
@@ -1,8 +1,8 @@
|
||||
<?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 Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
<?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\Resources\Json\JsonResource;
|
||||
|
||||
/** @mixin array{website_type: AdminWebsiteType} */
|
||||
/** @mixin array{website_type: WebsiteType} */
|
||||
class AdminAppBootstrapResource extends JsonResource
|
||||
{
|
||||
/** @return array<string, mixed> */
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
/** @var AdminWebsiteType $websiteType */
|
||||
/** @var WebsiteType $websiteType */
|
||||
$websiteType = $this->resource['website_type'];
|
||||
|
||||
return [
|
||||
'admin_website_type_code' => $websiteType->codigo,
|
||||
'site_title' => $websiteType->site_title,
|
||||
'website_type_code' => $websiteType->codigo,
|
||||
'primary_color' => $websiteType->primary_color,
|
||||
'secondary_color' => $websiteType->secondary_color,
|
||||
'danger_color' => $websiteType->danger_color,
|
||||
@@ -31,7 +30,6 @@ class AdminAppBootstrapResource extends JsonResource
|
||||
'login_header_footer_color' => $websiteType->login_header_footer_color,
|
||||
'site_logo' => $websiteType->siteLogo?->getTemporaryUrl(1440),
|
||||
'footer_logo' => $websiteType->footerLogo?->getTemporaryUrl(1440),
|
||||
'favicon' => $websiteType->favicon?->getTemporaryUrl(1440),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Bootstrap\Resources;
|
||||
namespace App\Domains\Bootstrap\Resources;
|
||||
|
||||
class ScannerBootstrapResource extends AdminAppBootstrapResource {}
|
||||
19
app/Domains/Bootstrap/Services/AdminAppBootstrapService.php
Normal file
19
app/Domains/Bootstrap/Services/AdminAppBootstrapService.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Bootstrap\Services;
|
||||
|
||||
use App\Domains\Tenant\Models\WebsiteType;
|
||||
|
||||
class AdminAppBootstrapService
|
||||
{
|
||||
/** @return array{website_type: WebsiteType} */
|
||||
public function get(string $domain): array
|
||||
{
|
||||
return [
|
||||
'website_type' => WebsiteType::query()
|
||||
->with(['siteLogo', 'footerLogo'])
|
||||
->where('dominio', $domain)
|
||||
->firstOrFail(),
|
||||
];
|
||||
}
|
||||
}
|
||||
19
app/Domains/Bootstrap/Services/ScannerBootstrapService.php
Normal file
19
app/Domains/Bootstrap/Services/ScannerBootstrapService.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Bootstrap\Services;
|
||||
|
||||
use App\Domains\Tenant\Models\WebsiteType;
|
||||
|
||||
class ScannerBootstrapService
|
||||
{
|
||||
/** @return array{website_type: WebsiteType} */
|
||||
public function get(string $domain): array
|
||||
{
|
||||
return [
|
||||
'website_type' => WebsiteType::query()
|
||||
->with(['siteLogo', 'footerLogo'])
|
||||
->where('scanner_domain', $domain)
|
||||
->firstOrFail(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Bootstrap\Services;
|
||||
namespace App\Domains\Bootstrap\Services;
|
||||
|
||||
use App\Domains\Core\Authorization\Enums\RoleCode;
|
||||
use App\Domains\Core\Tenant\Models\Tenant;
|
||||
use App\Domains\Core\Tenant\Services\TenantInformationService;
|
||||
use App\Domains\Core\Tenant\Support\TenantDomainNormalizer;
|
||||
use App\Domains\Authorization\Enums\RoleCode;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use App\Domains\Tenant\Services\TenantInformationService;
|
||||
use App\Domains\Tenant\Support\TenantDomainNormalizer;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
|
||||
class TenantBootstrapService
|
||||
@@ -32,13 +32,11 @@ class TenantBootstrapService
|
||||
return $this->tenantInformationService->load(
|
||||
$tenant,
|
||||
[
|
||||
'eventDateChanges',
|
||||
'menues' => fn ($query) => $query->whereHas(
|
||||
'roles',
|
||||
fn ($query) => $query->where('codigo', RoleCode::User->value)
|
||||
),
|
||||
$tenant->storefront_website_type_code === 'onticket_multi_event'
|
||||
? 'eventCategories' : 'categories' => fn ($query) => $query->orderBy('nombre'),
|
||||
'categories' => fn ($query) => $query->orderBy('nombre'),
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Core\Bootstrap\Controllers\AdminAppBootstrapController;
|
||||
use App\Domains\Bootstrap\Controllers\AdminAppBootstrapController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get(
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Core\Bootstrap\Controllers\TenantBootstrapController;
|
||||
use App\Domains\Bootstrap\Controllers\TenantBootstrapController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('tenants/bootstrap', TenantBootstrapController::class);
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Core\Bootstrap\Controllers\ScannerBootstrapController;
|
||||
use App\Domains\Bootstrap\Controllers\ScannerBootstrapController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get(
|
||||
@@ -1,13 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Commerce\Cart\Controllers;
|
||||
namespace App\Domains\Cart\Controllers;
|
||||
|
||||
use App\Domains\Commerce\Cart\Models\CartItem;
|
||||
use App\Domains\Commerce\Cart\Requests\AddCartItemRequest;
|
||||
use App\Domains\Commerce\Cart\Requests\UpdateCartItemQuantityRequest;
|
||||
use App\Domains\Commerce\Cart\Resources\CartResource;
|
||||
use App\Domains\Commerce\Cart\Services\CartService;
|
||||
use App\Domains\Core\Tenant\Models\Tenant;
|
||||
use App\Domains\Cart\Models\CartItem;
|
||||
use App\Domains\Cart\Requests\AddCartItemRequest;
|
||||
use App\Domains\Cart\Requests\UpdateCartItemQuantityRequest;
|
||||
use App\Domains\Cart\Resources\CartResource;
|
||||
use App\Domains\Cart\Services\CartService;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -1,17 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Commerce\Cart\Models;
|
||||
namespace App\Domains\Cart\Models;
|
||||
|
||||
use App\Domains\Core\Auth\Models\User;
|
||||
use App\Domains\Commerce\Catalog\Models\CatalogItem;
|
||||
use App\Domains\Commerce\Catalog\Models\Inventory;
|
||||
use App\Domains\Commerce\Catalog\Models\StockReservation;
|
||||
use App\Domains\Commerce\Catalog\Models\Variant;
|
||||
use App\Domains\Commerce\Catalog\Services\CatalogInventoryService;
|
||||
use App\Domains\Commerce\Catalog\Services\StockReservationService;
|
||||
use App\Domains\Commerce\Purchase\Models\Purchase;
|
||||
use App\Domains\Commerce\Purchase\Services\UserPurchaseLimitService;
|
||||
use App\Domains\Core\Tenant\Models\Tenant;
|
||||
use App\Domains\Auth\Models\User;
|
||||
use App\Domains\Catalog\Models\CatalogItem;
|
||||
use App\Domains\Catalog\Models\Inventory;
|
||||
use App\Domains\Catalog\Models\Variant;
|
||||
use App\Domains\Catalog\Services\CatalogInventoryService;
|
||||
use App\Domains\Catalog\Services\StockReservationService;
|
||||
use App\Domains\Purchase\Models\Purchase;
|
||||
use App\Domains\Purchase\Services\UserPurchaseLimitService;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -29,7 +28,6 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
'status',
|
||||
'origin',
|
||||
'current_purchase_id',
|
||||
'current_stock_reservation_id',
|
||||
])]
|
||||
class Cart extends Model
|
||||
{
|
||||
@@ -38,16 +36,6 @@ class Cart extends Model
|
||||
|
||||
protected $table = 'carritos';
|
||||
|
||||
public const STATUS_ACTIVE = 'active';
|
||||
|
||||
public const STATUS_CHECKOUT = 'checkout';
|
||||
|
||||
public const STATUS_CONVERTED = 'converted';
|
||||
|
||||
public const STATUS_EXPIRED = 'expired';
|
||||
|
||||
public const STATUS_ABANDONED = 'abandoned';
|
||||
|
||||
public const ORIGIN_USER = 'user';
|
||||
|
||||
public const ORIGIN_DIRECT_CHECKOUT = 'direct_checkout';
|
||||
@@ -57,7 +45,6 @@ class Cart extends Model
|
||||
return [
|
||||
'user_id' => 'integer',
|
||||
'current_purchase_id' => 'integer',
|
||||
'current_stock_reservation_id' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -97,15 +84,6 @@ class Cart extends Model
|
||||
return $this->belongsTo(Purchase::class, 'current_purchase_id');
|
||||
}
|
||||
|
||||
/** @return BelongsTo<StockReservation, $this> */
|
||||
public function currentStockReservation(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(
|
||||
StockReservation::class,
|
||||
'current_stock_reservation_id',
|
||||
);
|
||||
}
|
||||
|
||||
public function getTotalAmount(): float
|
||||
{
|
||||
$items = $this->relationLoaded('items')
|
||||
@@ -129,7 +107,6 @@ class Cart extends Model
|
||||
|
||||
return DB::transaction(function () use ($catalogItemId, $variantId, $quantity): CartItem {
|
||||
$this->invalidateCurrentCheckout();
|
||||
app(StockReservationService::class)->assertCartReservationUsable($this);
|
||||
$selectedItem = $this->resolveScopedItem($catalogItemId, $variantId, true);
|
||||
$cartQuantity = (int) $this->items()
|
||||
->where('catalog_item_id', $catalogItemId)
|
||||
@@ -163,11 +140,12 @@ class Cart extends Model
|
||||
'cantidad' => $quantity,
|
||||
]);
|
||||
} else {
|
||||
app(StockReservationService::class)->ensure($item, $selectedItem);
|
||||
$item->cantidad += $quantity;
|
||||
$item->save();
|
||||
}
|
||||
|
||||
app(StockReservationService::class)->syncCart($this);
|
||||
app(StockReservationService::class)->reserve($item, $selectedItem, $quantity);
|
||||
|
||||
return $item->fresh();
|
||||
});
|
||||
@@ -194,7 +172,6 @@ class Cart extends Model
|
||||
$excludedPurchaseId,
|
||||
): CartItem {
|
||||
$this->invalidateCurrentCheckout();
|
||||
app(StockReservationService::class)->assertCartReservationUsable($this);
|
||||
|
||||
/** @var CartItem $item */
|
||||
$item = $this->items()
|
||||
@@ -228,6 +205,7 @@ class Cart extends Model
|
||||
$nextAvailableQuantity,
|
||||
);
|
||||
|
||||
app(StockReservationService::class)->release($item, $currentSelection, $item->cantidad);
|
||||
$availableQuantity = $inventoryService->availableQuantity($nextSelection);
|
||||
|
||||
if ($availableQuantity !== null && $availableQuantity < $quantity) {
|
||||
@@ -244,10 +222,11 @@ class Cart extends Model
|
||||
->first();
|
||||
|
||||
if ($targetItem !== null) {
|
||||
app(StockReservationService::class)->ensure($targetItem, $nextSelection);
|
||||
$targetItem->cantidad += $quantity;
|
||||
$targetItem->save();
|
||||
app(StockReservationService::class)->reserve($targetItem, $nextSelection, $quantity);
|
||||
$item->delete();
|
||||
app(StockReservationService::class)->syncCart($this);
|
||||
|
||||
return $targetItem->fresh();
|
||||
}
|
||||
@@ -255,7 +234,7 @@ class Cart extends Model
|
||||
$item->variant_id = $variantId;
|
||||
$item->cantidad = $quantity;
|
||||
$item->save();
|
||||
app(StockReservationService::class)->syncCart($this);
|
||||
app(StockReservationService::class)->reserve($item, $nextSelection, $quantity);
|
||||
|
||||
return $item->fresh();
|
||||
}
|
||||
@@ -284,9 +263,16 @@ class Cart extends Model
|
||||
]);
|
||||
}
|
||||
|
||||
if ($delta > 0) {
|
||||
app(StockReservationService::class)->reserve($item, $currentSelection, $delta);
|
||||
}
|
||||
|
||||
if ($delta < 0) {
|
||||
app(StockReservationService::class)->release($item, $currentSelection, abs($delta));
|
||||
}
|
||||
|
||||
$item->cantidad = $quantity;
|
||||
$item->save();
|
||||
app(StockReservationService::class)->syncCart($this);
|
||||
|
||||
return $item->fresh();
|
||||
});
|
||||
@@ -296,7 +282,6 @@ class Cart extends Model
|
||||
{
|
||||
DB::transaction(function () use ($cartItemId): void {
|
||||
$this->invalidateCurrentCheckout();
|
||||
app(StockReservationService::class)->assertCartReservationUsable($this);
|
||||
|
||||
/** @var CartItem $item */
|
||||
$item = $this->items()
|
||||
@@ -304,8 +289,17 @@ class Cart extends Model
|
||||
->lockForUpdate()
|
||||
->firstOrFail();
|
||||
|
||||
$selectedItem = $this->resolveScopedItem(
|
||||
$item->catalog_item_id,
|
||||
$item->variant_id,
|
||||
true,
|
||||
);
|
||||
app(StockReservationService::class)->release(
|
||||
$item,
|
||||
$selectedItem,
|
||||
$item->cantidad,
|
||||
);
|
||||
$item->delete();
|
||||
app(StockReservationService::class)->syncCart($this);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -347,20 +341,13 @@ class Cart extends Model
|
||||
Purchase::STATUS_CREATED,
|
||||
Purchase::STATUS_PENDING_PAYMENT,
|
||||
], true)) {
|
||||
app(StockReservationService::class)->returnToCart($currentPurchase, $cart);
|
||||
$currentPurchase->update([
|
||||
'status' => Purchase::STATUS_SUPERSEDED,
|
||||
'expires_at' => null,
|
||||
]);
|
||||
|
||||
$this->current_purchase_id = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
app(StockReservationService::class)->releaseForPurchase(
|
||||
$currentPurchase,
|
||||
reason: StockReservationService::REASON_PURCHASE_SUPERSEDED,
|
||||
);
|
||||
app(StockReservationService::class)->detachFromPurchase($currentPurchase);
|
||||
self::query()
|
||||
->whereKey($cart->getKey())
|
||||
->where('current_purchase_id', $currentPurchase->getKey())
|
||||
@@ -375,7 +362,7 @@ class Cart extends Model
|
||||
): CatalogItem|Variant {
|
||||
$catalogItemQuery = CatalogItem::query()
|
||||
->whereKey($catalogItemId)
|
||||
->forTenantCatalog($this->tenant()->firstOrFail());
|
||||
->where('tenant_code', $this->tenant_codigo);
|
||||
|
||||
if ($lockForUpdate) {
|
||||
$catalogItemQuery->lockForUpdate();
|
||||
@@ -400,17 +387,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;
|
||||
}
|
||||
|
||||
@@ -441,12 +417,6 @@ class Cart extends Model
|
||||
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);
|
||||
$variant->setRelation('catalogItem', $catalogItem);
|
||||
$variant->setRelation('inventory', $inventory);
|
||||
@@ -1,13 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Commerce\Cart\Models;
|
||||
namespace App\Domains\Cart\Models;
|
||||
|
||||
use App\Domains\Commerce\Catalog\Models\CatalogItem;
|
||||
use App\Domains\Commerce\Catalog\Models\Variant;
|
||||
use App\Domains\Catalog\Models\CatalogItem;
|
||||
use App\Domains\Catalog\Models\StockReservation;
|
||||
use App\Domains\Catalog\Models\Variant;
|
||||
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\HasMany;
|
||||
|
||||
#[Fillable([
|
||||
'cart_id',
|
||||
@@ -55,4 +57,10 @@ class CartItem extends Model
|
||||
{
|
||||
return $this->variant ?? $this->catalogItem;
|
||||
}
|
||||
|
||||
/** @return HasMany<StockReservation, $this> */
|
||||
public function stockReservations(): HasMany
|
||||
{
|
||||
return $this->hasMany(StockReservation::class);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Commerce\Cart\Requests;
|
||||
namespace App\Domains\Cart\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Commerce\Cart\Requests;
|
||||
namespace App\Domains\Cart\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Commerce\Cart\Resources;
|
||||
namespace App\Domains\Cart\Resources;
|
||||
|
||||
use App\Domains\Commerce\Cart\Models\CartItem;
|
||||
use App\Domains\Commerce\Catalog\Enums\InventoryPolicy;
|
||||
use App\Domains\Commerce\Catalog\Models\Variant;
|
||||
use App\Domains\Core\Tenant\Models\Tenant;
|
||||
use App\Domains\Cart\Models\CartItem;
|
||||
use App\Domains\Catalog\Enums\InventoryPolicy;
|
||||
use App\Domains\Catalog\Models\Variant;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?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\Resources\Json\JsonResource;
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Commerce\Cart\Services;
|
||||
namespace App\Domains\Cart\Services;
|
||||
|
||||
use App\Domains\Core\Auth\Models\User;
|
||||
use App\Domains\Commerce\Cart\Models\Cart;
|
||||
use App\Domains\Commerce\Catalog\Exceptions\StockReservationExpiredException;
|
||||
use App\Domains\Commerce\Catalog\Services\ExpireStockReservationsService;
|
||||
use App\Domains\Core\Tenant\Models\Tenant;
|
||||
use App\Domains\Auth\Models\User;
|
||||
use App\Domains\Cart\Models\Cart;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Symfony\Component\HttpFoundation\Cookie;
|
||||
@@ -25,7 +22,7 @@ class CartService
|
||||
return $this->makeEmptyCart($tenant);
|
||||
}
|
||||
|
||||
$cart = $this->resolveCart($tenant, $resolvedIdentity['identity']);
|
||||
$cart = $this->findCart($tenant, $resolvedIdentity['identity']);
|
||||
|
||||
if ($cart === null) {
|
||||
return $this->makeEmptyCart($tenant);
|
||||
@@ -122,7 +119,7 @@ class CartService
|
||||
{
|
||||
$cart = new Cart([
|
||||
'tenant_codigo' => $tenant->codigo,
|
||||
'status' => Cart::STATUS_ACTIVE,
|
||||
'status' => 'active',
|
||||
]);
|
||||
|
||||
$cart->setRelation('items', collect());
|
||||
@@ -223,14 +220,12 @@ class CartService
|
||||
{
|
||||
return Cart::query()
|
||||
->where('tenant_codigo', $tenant->codigo)
|
||||
->where('origin', Cart::ORIGIN_USER)
|
||||
->whereIn('status', [Cart::STATUS_ACTIVE, Cart::STATUS_EXPIRED])
|
||||
->where('status', 'active')
|
||||
->when(
|
||||
$identity['user_id'] !== null,
|
||||
fn ($query) => $query->where('user_id', $identity['user_id']),
|
||||
fn ($query) => $query->where('guest_token', $identity['guest_token']),
|
||||
)
|
||||
->orderByRaw('CASE WHEN status = ? THEN 0 ELSE 1 END', [Cart::STATUS_ACTIVE])
|
||||
->first();
|
||||
}
|
||||
|
||||
@@ -239,7 +234,7 @@ class CartService
|
||||
*/
|
||||
protected function findCartOrFail(Tenant $tenant, array $identity): Cart
|
||||
{
|
||||
$cart = $this->resolveCart($tenant, $identity, replaceExpired: false);
|
||||
$cart = $this->findCart($tenant, $identity);
|
||||
|
||||
if ($cart === null) {
|
||||
throw new NotFoundHttpException('Cart not found.');
|
||||
@@ -252,73 +247,10 @@ class CartService
|
||||
* @param array{user_id: ?int, guest_token: ?string} $identity
|
||||
*/
|
||||
protected function findOrCreateCart(Tenant $tenant, array $identity): Cart
|
||||
{
|
||||
return $this->resolveCart($tenant, $identity)
|
||||
?? $this->createCart($tenant, $identity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{user_id: ?int, guest_token: ?string} $identity
|
||||
*/
|
||||
protected function resolveCart(
|
||||
Tenant $tenant,
|
||||
array $identity,
|
||||
bool $replaceExpired = true,
|
||||
): ?Cart {
|
||||
$cart = $this->findCart($tenant, $identity);
|
||||
|
||||
if ($cart?->status === Cart::STATUS_ACTIVE
|
||||
&& $cart->current_stock_reservation_id !== null
|
||||
&& app(ExpireStockReservationsService::class)
|
||||
->expireIfOverdue($cart->current_stock_reservation_id)) {
|
||||
$cart = $this->findCart($tenant, $identity);
|
||||
}
|
||||
|
||||
if ($cart?->status === Cart::STATUS_EXPIRED) {
|
||||
if (! $replaceExpired) {
|
||||
throw new StockReservationExpiredException;
|
||||
}
|
||||
|
||||
return $this->replaceExpiredCart($cart, $tenant, $identity);
|
||||
}
|
||||
|
||||
if ($cart !== null) {
|
||||
return $cart;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{user_id: ?int, guest_token: ?string} $identity
|
||||
*/
|
||||
protected function replaceExpiredCart(Cart $expiredCart, Tenant $tenant, array $identity): Cart
|
||||
{
|
||||
return DB::transaction(function () use ($expiredCart, $tenant, $identity): Cart {
|
||||
/** @var Cart|null $lockedCart */
|
||||
$lockedCart = Cart::query()->lockForUpdate()->find($expiredCart->getKey());
|
||||
|
||||
if ($lockedCart?->status === Cart::STATUS_EXPIRED) {
|
||||
$lockedCart->update([
|
||||
'status' => Cart::STATUS_ABANDONED,
|
||||
'current_purchase_id' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->findCart($tenant, $identity)
|
||||
?? $this->createCart($tenant, $identity);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{user_id: ?int, guest_token: ?string} $identity
|
||||
*/
|
||||
protected function createCart(Tenant $tenant, array $identity): Cart
|
||||
{
|
||||
$attributes = [
|
||||
'tenant_codigo' => $tenant->codigo,
|
||||
'status' => Cart::STATUS_ACTIVE,
|
||||
'origin' => Cart::ORIGIN_USER,
|
||||
'status' => 'active',
|
||||
];
|
||||
|
||||
if ($identity['user_id'] !== null) {
|
||||
123
app/Domains/Cart/Services/ExpireCartReservationsService.php
Normal file
123
app/Domains/Cart/Services/ExpireCartReservationsService.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Cart\Services;
|
||||
|
||||
use App\Domains\Cart\Models\Cart;
|
||||
use App\Domains\Cart\Models\CartItem;
|
||||
use App\Domains\Catalog\Models\Inventory;
|
||||
use App\Domains\Catalog\Models\StockReservation;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ExpireCartReservationsService
|
||||
{
|
||||
public function expireOverdue(): int
|
||||
{
|
||||
$expiredItems = 0;
|
||||
$lastCartItemId = 0;
|
||||
|
||||
do {
|
||||
$cartItemIds = StockReservation::query()
|
||||
->where('status', StockReservation::STATUS_ACTIVE)
|
||||
->whereNull('purchase_id')
|
||||
->whereNotNull('cart_item_id')
|
||||
->whereNotNull('expires_at')
|
||||
->where('expires_at', '<=', now())
|
||||
->where('cart_item_id', '>', $lastCartItemId)
|
||||
->whereHas('cartItem.cart', fn ($query) => $query->where('status', 'active'))
|
||||
->select('cart_item_id')
|
||||
->distinct()
|
||||
->orderBy('cart_item_id')
|
||||
->limit(500)
|
||||
->pluck('cart_item_id');
|
||||
|
||||
foreach ($cartItemIds as $cartItemId) {
|
||||
$lastCartItemId = (int) $cartItemId;
|
||||
|
||||
if ($this->expireCartItem($lastCartItemId)) {
|
||||
$expiredItems++;
|
||||
}
|
||||
}
|
||||
} while ($cartItemIds->count() === 500);
|
||||
|
||||
return $expiredItems;
|
||||
}
|
||||
|
||||
private function expireCartItem(int $cartItemId): bool
|
||||
{
|
||||
/** @var CartItem|null $candidate */
|
||||
$candidate = CartItem::query()->select(['id', 'cart_id'])->find($cartItemId);
|
||||
if ($candidate === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($candidate, $cartItemId): bool {
|
||||
/** @var Cart|null $cart */
|
||||
$cart = Cart::query()
|
||||
->whereKey($candidate->cart_id)
|
||||
->where('status', 'active')
|
||||
->lockForUpdate()
|
||||
->first();
|
||||
|
||||
if ($cart === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var CartItem|null $cartItem */
|
||||
$cartItem = $cart->items()
|
||||
->whereKey($cartItemId)
|
||||
->lockForUpdate()
|
||||
->first();
|
||||
|
||||
if ($cartItem === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$reservations = StockReservation::query()
|
||||
->where('cart_item_id', $cartItem->getKey())
|
||||
->where('status', StockReservation::STATUS_ACTIVE)
|
||||
->orderBy('inventory_id')
|
||||
->lockForUpdate()
|
||||
->get();
|
||||
|
||||
if (
|
||||
$reservations->isEmpty()
|
||||
|| $reservations->contains(
|
||||
fn (StockReservation $reservation): bool => $reservation->purchase_id !== null
|
||||
|| $reservation->expires_at === null
|
||||
|| $reservation->expires_at->isFuture(),
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$inventories = Inventory::query()
|
||||
->whereKey($reservations->pluck('inventory_id'))
|
||||
->orderBy('id')
|
||||
->lockForUpdate()
|
||||
->get()
|
||||
->keyBy('id');
|
||||
|
||||
foreach ($reservations as $reservation) {
|
||||
$inventory = $inventories->get($reservation->inventory_id)
|
||||
?? throw new \InvalidArgumentException('No se encontro el inventario reservado.');
|
||||
|
||||
$inventory->release((int) $reservation->quantity);
|
||||
$reservation->update([
|
||||
'quantity' => 0,
|
||||
'status' => StockReservation::STATUS_EXPIRED,
|
||||
'expires_at' => null,
|
||||
'released_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
$cartItem->delete();
|
||||
|
||||
if (! $cart->items()->exists()) {
|
||||
$cart->update(['status' => 'expired']);
|
||||
$cart->delete();
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Commerce\Cart\Services;
|
||||
namespace App\Domains\Cart\Services;
|
||||
|
||||
use App\Domains\Core\Auth\Models\User;
|
||||
use App\Domains\Commerce\Cart\Models\Cart;
|
||||
use App\Domains\Auth\Models\User;
|
||||
use App\Domains\Cart\Models\Cart;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class GuestCartMergeService
|
||||
@@ -6,12 +6,13 @@ Gestiona el carrito activo de un tenant tanto para visitantes como para usuarios
|
||||
|
||||
## Modelo
|
||||
|
||||
- `Cart`: pertenece a un tenant y opcionalmente a un usuario; calcula el total, permite agregar, actualizar o quitar ítems y apunta a su reserva de stock vigente mediante `current_stock_reservation_id`.
|
||||
- `Cart`: pertenece a un tenant y opcionalmente a un usuario; calcula el total y permite agregar, actualizar o quitar ítems.
|
||||
- `CartItem`: referencia un `CatalogItem` y, opcionalmente, una `Variant`; sólo persiste la selección y cantidad, y expone siempre los datos vigentes del catálogo.
|
||||
|
||||
## Servicios
|
||||
|
||||
- `CartService`: obtiene el carrito, modifica ítems y administra la cookie del token invitado.
|
||||
- `ExpireCartReservationsService`: libera las reservas vencidas de carritos activos y elimina los carritos que quedan vacíos.
|
||||
- `GuestCartMergeService`: incorpora el carrito invitado al usuario cuando este se autentica.
|
||||
|
||||
## Endpoints
|
||||
@@ -33,6 +34,4 @@ Depende de `Catalog` para productos y variantes, de `Tenant` para aislar datos y
|
||||
|
||||
Un carrito puede pasar a `checkout`. Las compras directas usan un carrito técnico con `origin=direct_checkout`; los carritos normales conservan `origin=user` y pueden restaurarse al cancelar o vencer la compra.
|
||||
|
||||
Cada edición sincroniza una única reserva para el carrito completo. Si varios ítems o bundles consumen el mismo inventario, se persiste una sola línea con la cantidad agregada. Al editar durante checkout, la compra anterior queda `superseded`, se desvincula y el carrito conserva la misma reserva activa con sus líneas actualizadas.
|
||||
|
||||
El comando unificado `php artisan reservations:expire` recorre una sola vez las reservas activas cuyo `expires_at` haya vencido. Cuando pertenecen a un carrito, conserva la reserva y sus líneas como historial, libera el stock como conjunto y cambia el carrito asociado a `expired` sin eliminar sus ítems. Al volver a resolver ese carrito desde la API, el anterior pasa automáticamente a `abandoned` y se crea uno activo y vacío para la misma identidad. El cliente nunca necesita reiniciarlo explícitamente. La API también materializa este vencimiento al acceder al carrito aunque el comando programado todavía no haya corrido.
|
||||
El comando unificado `php artisan reservations:expire` procesa primero las compras vencidas y luego las reservas activas sin compra cuyo `expires_at` haya vencido. Se ejecuta cada minuto mediante el scheduler, conserva la fila de reserva con estado `expired`, elimina el ítem abandonado y elimina lógicamente el carrito cuando queda vacío. Cada intento registra sus resultados o su error en el log diario `storage/logs/commands/commands-AAAA-MM-DD.log`.
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Domains\Commerce\Cart\Controllers\CartController;
|
||||
use App\Domains\Cart\Controllers\CartController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::prefix('tenants/{tenant:codigo}')
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Commerce\Catalog\Controllers\AdminApp;
|
||||
namespace App\Domains\Catalog\Controllers\AdminApp;
|
||||
|
||||
use App\Domains\Commerce\Catalog\Models\FeaturedGroup;
|
||||
use App\Domains\Commerce\Catalog\Requests\AdminApp\UpsertOnTicketFeaturedGroupRequest;
|
||||
use App\Domains\Commerce\Catalog\Resources\AdminApp\OnTicketFeaturedGroupResource;
|
||||
use App\Domains\Commerce\Catalog\Services\OnTicketFeaturedGroupService;
|
||||
use App\Domains\Core\Tenant\Models\Tenant;
|
||||
use App\Domains\Catalog\Models\FeaturedGroup;
|
||||
use App\Domains\Catalog\Requests\AdminApp\UpsertOnTicketFeaturedGroupRequest;
|
||||
use App\Domains\Catalog\Resources\AdminApp\OnTicketFeaturedGroupResource;
|
||||
use App\Domains\Catalog\Services\OnTicketFeaturedGroupService;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -58,7 +58,7 @@ class OnTicketFeaturedGroupController extends Controller
|
||||
{
|
||||
$tenant = $request->user()->tenant()->firstOrFail();
|
||||
|
||||
abort_unless(in_array($tenant->storefront_website_type_code, ['onticket', 'onticket_multi_event'], true), 404);
|
||||
abort_unless($tenant->website_type_code === 'onticket', 404);
|
||||
|
||||
return $tenant;
|
||||
}
|
||||
@@ -1,25 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Commerce\Catalog\Controllers;
|
||||
namespace App\Domains\Catalog\Controllers;
|
||||
|
||||
use App\Domains\Commerce\Cart\Services\CartService;
|
||||
use App\Domains\Commerce\Catalog\Models\CatalogItem;
|
||||
use App\Domains\Commerce\Catalog\Models\Category;
|
||||
use App\Domains\Commerce\Catalog\Models\FeaturedGroup;
|
||||
use App\Domains\Commerce\Catalog\Requests\CatalogItemDetailRequest;
|
||||
use App\Domains\Commerce\Catalog\Requests\CatalogVariantOptionsRequest;
|
||||
use App\Domains\Commerce\Catalog\Requests\CategoryPageRequest;
|
||||
use App\Domains\Commerce\Catalog\Requests\FeaturedGroupPageRequest;
|
||||
use App\Domains\Commerce\Catalog\Requests\SearchCatalogItemsRequest;
|
||||
use App\Domains\Commerce\Catalog\Resources\CatalogFeaturedGroupResource;
|
||||
use App\Domains\Commerce\Catalog\Resources\CatalogItemDetailResource;
|
||||
use App\Domains\Commerce\Catalog\Resources\CatalogSearchItemResource;
|
||||
use App\Domains\Commerce\Catalog\Resources\CatalogVariantOptionsResource;
|
||||
use App\Domains\Commerce\Catalog\Services\CatalogItemAllowanceService;
|
||||
use App\Domains\Commerce\Catalog\Services\CatalogService;
|
||||
use App\Domains\Commerce\Catalog\Services\FeaturedGroupService;
|
||||
use App\Domains\Commerce\Catalog\Services\VariantSelectionService;
|
||||
use App\Domains\Core\Tenant\Models\Tenant;
|
||||
use App\Domains\Cart\Services\CartService;
|
||||
use App\Domains\Catalog\Models\CatalogItem;
|
||||
use App\Domains\Catalog\Models\Category;
|
||||
use App\Domains\Catalog\Models\FeaturedGroup;
|
||||
use App\Domains\Catalog\Requests\CatalogItemDetailRequest;
|
||||
use App\Domains\Catalog\Requests\CatalogVariantOptionsRequest;
|
||||
use App\Domains\Catalog\Requests\CategoryPageRequest;
|
||||
use App\Domains\Catalog\Requests\FeaturedGroupPageRequest;
|
||||
use App\Domains\Catalog\Requests\SearchCatalogItemsRequest;
|
||||
use App\Domains\Catalog\Requests\StoreCatalogItemRequest;
|
||||
use App\Domains\Catalog\Resources\CatalogFeaturedGroupResource;
|
||||
use App\Domains\Catalog\Resources\CatalogItemDetailResource;
|
||||
use App\Domains\Catalog\Resources\CatalogItemResource;
|
||||
use App\Domains\Catalog\Resources\CatalogSearchItemResource;
|
||||
use App\Domains\Catalog\Resources\CatalogVariantOptionsResource;
|
||||
use App\Domains\Catalog\Services\CatalogItemAllowanceService;
|
||||
use App\Domains\Catalog\Services\CatalogService;
|
||||
use App\Domains\Catalog\Services\FeaturedGroupService;
|
||||
use App\Domains\Catalog\Services\VariantSelectionService;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -38,7 +40,7 @@ class CatalogController extends Controller
|
||||
return response()->json($featuredGroups->map(
|
||||
fn (FeaturedGroup $featuredGroup): array => (new CatalogFeaturedGroupResource(
|
||||
$featuredGroup,
|
||||
$featuredGroupService->itemsResponse($featuredGroup, $tenant, 1, $this->userId($request)),
|
||||
$featuredGroupService->itemsResponse($featuredGroup, 1, $this->userId($request)),
|
||||
))->resolve()
|
||||
));
|
||||
}
|
||||
@@ -100,7 +102,6 @@ class CatalogController extends Controller
|
||||
|
||||
return response()->json($featuredGroupService->itemsResponse(
|
||||
$featuredGroup,
|
||||
$tenant,
|
||||
$page,
|
||||
$this->userId($request),
|
||||
));
|
||||
@@ -113,8 +114,7 @@ class CatalogController extends Controller
|
||||
CatalogService $catalogService,
|
||||
CatalogItemAllowanceService $allowances,
|
||||
): CatalogItemDetailResource {
|
||||
abort_unless($catalogItem->tenant_code === $tenant->codigo
|
||||
&& ($tenant->active_event_id === null || $catalogItem->event_id === $tenant->active_event_id), 404);
|
||||
abort_unless($catalogItem->tenant_code === $tenant->codigo, 404);
|
||||
|
||||
$variantId = $request->validated('variant_id');
|
||||
|
||||
@@ -123,6 +123,10 @@ class CatalogController extends Controller
|
||||
$variantId === null ? null : (int) $variantId,
|
||||
);
|
||||
$allowances->attach(collect([$item]), $this->userId($request));
|
||||
abort_unless($allowances->availability(
|
||||
$item->availableStock(),
|
||||
$item->getAttribute('remaining_user_quota'),
|
||||
)->isVisible(), 404);
|
||||
|
||||
return CatalogItemDetailResource::make($item);
|
||||
}
|
||||
@@ -134,8 +138,7 @@ class CatalogController extends Controller
|
||||
VariantSelectionService $variantSelectionService,
|
||||
CartService $cartService,
|
||||
): CatalogVariantOptionsResource {
|
||||
abort_unless($catalogItem->tenant_code === $tenant->codigo
|
||||
&& ($tenant->active_event_id === null || $catalogItem->event_id === $tenant->active_event_id), 404);
|
||||
abort_unless($catalogItem->tenant_code === $tenant->codigo, 404);
|
||||
|
||||
$includedVariantId = null;
|
||||
$cartItemId = $request->validated('cart_item_id');
|
||||
@@ -164,6 +167,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
|
||||
{
|
||||
$userId = $request->user()?->getAuthIdentifier() ?? Auth::guard('sanctum')->id();
|
||||
10
app/Domains/Catalog/Enums/AvailabilityEffect.php
Normal file
10
app/Domains/Catalog/Enums/AvailabilityEffect.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Catalog\Enums;
|
||||
|
||||
enum AvailabilityEffect: string
|
||||
{
|
||||
case Hide = 'hide';
|
||||
case Restrict = 'restrict';
|
||||
case Notice = 'notice';
|
||||
}
|
||||
11
app/Domains/Catalog/Enums/CatalogAction.php
Normal file
11
app/Domains/Catalog/Enums/CatalogAction.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Catalog\Enums;
|
||||
|
||||
enum CatalogAction: string
|
||||
{
|
||||
case SelectVariant = 'select_variant';
|
||||
case ChangeQuantity = 'change_quantity';
|
||||
case AddToCart = 'add_to_cart';
|
||||
case BuyNow = 'buy_now';
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Commerce\Catalog\Enums;
|
||||
namespace App\Domains\Catalog\Enums;
|
||||
|
||||
enum CatalogItemType: string
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Commerce\Catalog\Enums;
|
||||
namespace App\Domains\Catalog\Enums;
|
||||
|
||||
enum FeaturedGroupSource: string
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Commerce\Catalog\Enums;
|
||||
namespace App\Domains\Catalog\Enums;
|
||||
|
||||
enum GroupLayout: string
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Commerce\Catalog\Enums;
|
||||
namespace App\Domains\Catalog\Enums;
|
||||
|
||||
enum InventoryPolicy: string
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Commerce\Catalog\Enums;
|
||||
namespace App\Domains\Catalog\Enums;
|
||||
|
||||
enum InventorySubject: string
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Commerce\Catalog\Enums;
|
||||
namespace App\Domains\Catalog\Enums;
|
||||
|
||||
enum ProductLayout: string
|
||||
{
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Commerce\Catalog\Models;
|
||||
namespace App\Domains\Catalog\Models;
|
||||
|
||||
use App\Domains\Ticketing\Event\Models\EventDate;
|
||||
use App\Shared\Enums\FieldType;
|
||||
use App\Domains\Core\Tenant\Models\Tenant;
|
||||
use App\Domains\Event\Models\EventDate;
|
||||
use App\Domains\Shared\Enums\FieldType;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -59,8 +59,6 @@ class Attribute extends Model
|
||||
public function eventDates(): HasMany
|
||||
{
|
||||
return $this->hasMany(EventDate::class, 'tenant_code', 'tenant_codigo')
|
||||
->whereNull('rescheduled_to_event_date_id')
|
||||
->whereNull('suspended_at')
|
||||
->orderBy('date')
|
||||
->orderBy('time_start');
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
<?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\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user