initial commit

This commit is contained in:
bastien
2026-06-28 12:29:53 +02:00
parent 03e85d2f9d
commit b2443b654c
220 changed files with 24969 additions and 1 deletions
+43
View File
@@ -0,0 +1,43 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
#[Fillable(['actor_type', 'actor_id', 'organization_id', 'establishment_id', 'action', 'target_type', 'target_id', 'before_data', 'after_data', 'ip_address'])]
class AuditLog extends Model
{
public const UPDATED_AT = null;
protected function casts(): array
{
return [
'before_data' => 'array',
'after_data' => 'array',
'created_at' => 'datetime',
];
}
public function actor(): MorphTo
{
return $this->morphTo();
}
public function organization(): BelongsTo
{
return $this->belongsTo(Organization::class);
}
public function establishment(): BelongsTo
{
return $this->belongsTo(Establishment::class);
}
public function target(): MorphTo
{
return $this->morphTo();
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace App\Models;
use App\Models\Concerns\HasUuid;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[Fillable(['user_id', 'machine_id', 'slot_start', 'slot_end', 'booking_fee', 'reserved_amount', 'penalty_amount', 'status', 'cancelled_at', 'penalty_applied_at'])]
class Booking extends Model
{
use HasUuid;
protected function casts(): array
{
return [
'slot_start' => 'datetime',
'slot_end' => 'datetime',
'booking_fee' => 'decimal:2',
'reserved_amount' => 'decimal:2',
'penalty_amount' => 'decimal:2',
'status' => 'string',
'cancelled_at' => 'datetime',
'penalty_applied_at' => 'datetime',
];
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function machine(): BelongsTo
{
return $this->belongsTo(Machine::class);
}
public function washes(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Wash::class);
}
public function isCancellable(): bool
{
return in_array($this->status, ['pending', 'confirmed'], true)
&& $this->slot_start->isFuture();
}
}
@@ -0,0 +1,18 @@
<?php
namespace App\Models\Concerns;
use App\Models\Organization;
use Illuminate\Database\Eloquent\Builder;
trait BelongsToOrganization
{
public function scopeForOrganization(Builder $query, Organization|int $organization): Builder
{
$organizationId = $organization instanceof Organization ? $organization->id : $organization;
return $query->whereHas('establishment', function (Builder $query) use ($organizationId) {
$query->where('organization_id', $organizationId);
});
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace App\Models\Concerns;
use Illuminate\Support\Str;
trait HasUuid
{
protected static function bootHasUuid(): void
{
static::creating(function ($model) {
if (empty($model->uuid)) {
$model->uuid = (string) Str::uuid();
}
});
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOrganization;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[Fillable(['establishment_id', 'stat_date', 'total_washes', 'total_revenue', 'bookings_count', 'no_show_count', 'top_up_amount', 'occupancy_rate'])]
class DailyEstablishmentStat extends Model
{
use BelongsToOrganization;
protected function casts(): array
{
return [
'stat_date' => 'date',
'total_washes' => 'integer',
'total_revenue' => 'decimal:2',
'bookings_count' => 'integer',
'no_show_count' => 'integer',
'top_up_amount' => 'decimal:2',
'occupancy_rate' => 'decimal:2',
];
}
public function establishment(): BelongsTo
{
return $this->belongsTo(Establishment::class);
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
namespace App\Models;
use App\Models\Concerns\HasUuid;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
#[Fillable(['organization_id', 'name', 'address', 'city', 'zip_code', 'latitude', 'longitude', 'timezone', 'is_active'])]
class Establishment extends Model
{
use HasUuid;
protected function casts(): array
{
return [
'latitude' => 'decimal:8',
'longitude' => 'decimal:8',
'is_active' => 'boolean',
];
}
public function organization(): BelongsTo
{
return $this->belongsTo(Organization::class);
}
public function machines(): HasMany
{
return $this->hasMany(Machine::class);
}
public function pricingRules(): HasMany
{
return $this->hasMany(PricingRule::class);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[Fillable(['user_id', 'type', 'accepted', 'policy_version', 'policy_text_hash', 'source', 'consent_language', 'ip_address', 'user_agent', 'accepted_at', 'revoked_at'])]
class GdprConsent extends Model
{
public $timestamps = false;
protected function casts(): array
{
return [
'type' => 'string',
'accepted' => 'boolean',
'source' => 'string',
'accepted_at' => 'datetime',
'revoked_at' => 'datetime',
];
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
+63
View File
@@ -0,0 +1,63 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOrganization;
use App\Models\Concerns\HasUuid;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
#[Fillable(['establishment_id', 'name', 'type', 'qr_code', 'status', 'current_user_id', 'cycle_started_at', 'cycle_ends_at', 'last_heartbeat_at'])]
class Machine extends Model
{
use BelongsToOrganization, HasUuid;
protected function casts(): array
{
return [
'type' => 'string',
'status' => 'string',
'cycle_started_at' => 'datetime',
'cycle_ends_at' => 'datetime',
'last_heartbeat_at' => 'datetime',
];
}
public function establishment(): BelongsTo
{
return $this->belongsTo(Establishment::class);
}
public function currentUser(): BelongsTo
{
return $this->belongsTo(User::class, 'current_user_id');
}
public function bookings(): HasMany
{
return $this->hasMany(Booking::class);
}
public function washes(): HasMany
{
return $this->hasMany(Wash::class);
}
public function machineIntegration(): HasOne
{
return $this->hasOne(MachineIntegration::class);
}
public function integration(): HasOne
{
return $this->machineIntegration();
}
public function commands(): HasMany
{
return $this->hasMany(MachineCommand::class);
}
}
+40
View File
@@ -0,0 +1,40 @@
<?php
namespace App\Models;
use App\Models\Concerns\HasUuid;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[Fillable(['machine_id', 'provider', 'command_type', 'payload', 'status', 'external_reference', 'correlation_id', 'requested_by_user_id', 'requested_by_supervisor_id', 'sent_at', 'responded_at'])]
class MachineCommand extends Model
{
use HasUuid;
protected function casts(): array
{
return [
'command_type' => 'string',
'payload' => 'array',
'status' => 'string',
'sent_at' => 'datetime',
'responded_at' => 'datetime',
];
}
public function machine(): BelongsTo
{
return $this->belongsTo(Machine::class);
}
public function requestedByUser(): BelongsTo
{
return $this->belongsTo(User::class, 'requested_by_user_id');
}
public function requestedBySupervisor(): BelongsTo
{
return $this->belongsTo(Supervisor::class, 'requested_by_supervisor_id');
}
}
+28
View File
@@ -0,0 +1,28 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[Fillable(['machine_id', 'provider', 'external_event_id', 'event_type', 'payload', 'occurred_at', 'received_at', 'processed_at', 'processing_status'])]
class MachineEvent extends Model
{
protected function casts(): array
{
return [
'event_type' => 'string',
'payload' => 'array',
'occurred_at' => 'datetime',
'received_at' => 'datetime',
'processed_at' => 'datetime',
'processing_status' => 'string',
];
}
public function machine(): BelongsTo
{
return $this->belongsTo(Machine::class);
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[Fillable(['machine_id', 'provider', 'external_machine_id', 'external_site_id', 'mode', 'config', 'is_active'])]
class MachineIntegration extends Model
{
protected function casts(): array
{
return [
'mode' => 'string',
'config' => 'array',
'is_active' => 'boolean',
];
}
public function machine(): BelongsTo
{
return $this->belongsTo(Machine::class);
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[Fillable(['machine_id', 'previous_status', 'new_status', 'source', 'reason'])]
class MachineStatusHistory extends Model
{
public const UPDATED_AT = null;
protected function casts(): array
{
return [
'created_at' => 'datetime',
];
}
public function machine(): BelongsTo
{
return $this->belongsTo(Machine::class);
}
}
+26
View File
@@ -0,0 +1,26 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[Fillable(['user_id', 'transaction_enabled', 'reminder_enabled', 'marketing_enabled', 'system_enabled'])]
class NotificationPreference extends Model
{
protected function casts(): array
{
return [
'transaction_enabled' => 'boolean',
'reminder_enabled' => 'boolean',
'marketing_enabled' => 'boolean',
'system_enabled' => 'boolean',
];
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace App\Models;
use App\Models\Concerns\HasUuid;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
#[Fillable(['name', 'code', 'is_active'])]
class Organization extends Model
{
use HasUuid;
protected function casts(): array
{
return [
'is_active' => 'boolean',
];
}
public function establishments(): HasMany
{
return $this->hasMany(Establishment::class);
}
public function supervisors(): HasMany
{
return $this->hasMany(Supervisor::class);
}
}
+28
View File
@@ -0,0 +1,28 @@
<?php
namespace App\Models;
use App\Models\Concerns\HasUuid;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[Fillable(['user_id', 'provider', 'provider_payment_id', 'amount', 'currency', 'status', 'idempotency_key', 'return_url', 'raw_payload'])]
class PaymentTransaction extends Model
{
use HasUuid;
protected function casts(): array
{
return [
'amount' => 'decimal:2',
'status' => 'string',
'raw_payload' => 'array',
];
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOrganization;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[Fillable(['establishment_id', 'machine_id', 'machine_type', 'day_type', 'slot_start', 'slot_end', 'price', 'label', 'requires_app', 'priority', 'is_active'])]
class PricingRule extends Model
{
use BelongsToOrganization;
protected function casts(): array
{
return [
'machine_type' => 'string',
'day_type' => 'string',
'slot_start' => 'datetime:H:i:s',
'slot_end' => 'datetime:H:i:s',
'price' => 'decimal:2',
'requires_app' => 'boolean',
'priority' => 'integer',
'is_active' => 'boolean',
];
}
public function establishment(): BelongsTo
{
return $this->belongsTo(Establishment::class);
}
public function machine(): BelongsTo
{
return $this->belongsTo(Machine::class);
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace App\Models;
use App\Models\Concerns\BelongsToOrganization;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[Fillable(['establishment_id', 'machine_type', 'discount_type', 'discount_value', 'starts_at', 'ends_at', 'description', 'is_active'])]
class Promotion extends Model
{
use BelongsToOrganization;
protected function casts(): array
{
return [
'machine_type' => 'string',
'discount_type' => 'string',
'discount_value' => 'decimal:2',
'starts_at' => 'datetime',
'ends_at' => 'datetime',
'is_active' => 'boolean',
];
}
public function establishment(): BelongsTo
{
return $this->belongsTo(Establishment::class);
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[Fillable(['user_id', 'type', 'title', 'body', 'data', 'status', 'sent_at'])]
class PushNotification extends Model
{
protected function casts(): array
{
return [
'data' => 'array',
'status' => 'string',
'sent_at' => 'datetime',
];
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace App\Models;
use App\Models\Concerns\HasUuid;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Hidden;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
#[Fillable(['organization_id', 'establishment_id', 'first_name', 'last_name', 'email', 'password', 'role', 'is_active'])]
#[Hidden(['password', 'remember_token'])]
class Supervisor extends Authenticatable
{
use HasApiTokens, HasUuid, Notifiable;
protected function casts(): array
{
return [
'role' => 'string',
'is_active' => 'boolean',
'last_login_at' => 'datetime',
'password' => 'hashed',
];
}
public function organization(): BelongsTo
{
return $this->belongsTo(Organization::class);
}
public function establishment(): BelongsTo
{
return $this->belongsTo(Establishment::class);
}
}
+67
View File
@@ -0,0 +1,67 @@
<?php
namespace App\Models;
use App\Models\Concerns\HasUuid;
use Database\Factories\UserFactory;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Hidden;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
#[Fillable(['first_name', 'last_name', 'email', 'phone', 'birthdate', 'password', 'locale'])]
#[Hidden(['password', 'remember_token'])]
class User extends Authenticatable
{
/** @use HasFactory<UserFactory> */
use HasApiTokens, HasFactory, HasUuid, Notifiable, SoftDeletes;
protected function casts(): array
{
return [
'birthdate' => 'date',
'email_verified_at' => 'datetime',
'anonymized_at' => 'datetime',
'is_active' => 'boolean',
'password' => 'hashed',
];
}
protected function fullName(): Attribute
{
return Attribute::get(
fn (): string => trim("{$this->first_name} {$this->last_name}")
);
}
public function wallet(): HasOne
{
return $this->hasOne(Wallet::class);
}
public function bookings(): HasMany
{
return $this->hasMany(Booking::class);
}
public function washes(): HasMany
{
return $this->hasMany(Wash::class);
}
public function gdprConsents(): HasMany
{
return $this->hasMany(GdprConsent::class);
}
public function notificationPreference(): HasOne
{
return $this->hasOne(NotificationPreference::class);
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[Fillable(['user_id', 'platform', 'push_token', 'app_version', 'device_name', 'last_seen_at', 'revoked_at'])]
class UserDevice extends Model
{
protected function casts(): array
{
return [
'platform' => 'string',
'last_seen_at' => 'datetime',
'revoked_at' => 'datetime',
];
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
#[Fillable(['user_id', 'currency', 'current_balance', 'status'])]
class Wallet extends Model
{
protected function casts(): array
{
return [
'current_balance' => 'decimal:2',
'status' => 'string',
];
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function walletTransactions(): HasMany
{
return $this->hasMany(WalletTransaction::class);
}
public function isActive(): bool
{
return $this->status === 'active';
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
namespace App\Models;
use App\Models\Concerns\HasUuid;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
#[Fillable(['wallet_id', 'type', 'amount', 'balance_before', 'balance_after', 'source_type', 'source_id', 'idempotency_key', 'metadata'])]
class WalletTransaction extends Model
{
use HasUuid;
public const UPDATED_AT = null;
protected function casts(): array
{
return [
'type' => 'string',
'amount' => 'decimal:2',
'balance_before' => 'decimal:2',
'balance_after' => 'decimal:2',
'metadata' => 'array',
'created_at' => 'datetime',
];
}
public function wallet(): BelongsTo
{
return $this->belongsTo(Wallet::class);
}
public function source(): MorphTo
{
return $this->morphTo();
}
}
+46
View File
@@ -0,0 +1,46 @@
<?php
namespace App\Models;
use App\Models\Concerns\HasUuid;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[Fillable(['user_id', 'machine_id', 'booking_id', 'machine_command_id', 'trigger_method', 'status', 'program', 'started_at', 'ended_at', 'duration_minutes', 'cost'])]
class Wash extends Model
{
use HasUuid;
protected function casts(): array
{
return [
'trigger_method' => 'string',
'status' => 'string',
'started_at' => 'datetime',
'ended_at' => 'datetime',
'duration_minutes' => 'integer',
'cost' => 'decimal:2',
];
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function machine(): BelongsTo
{
return $this->belongsTo(Machine::class);
}
public function booking(): BelongsTo
{
return $this->belongsTo(Booking::class);
}
public function machineCommand(): BelongsTo
{
return $this->belongsTo(MachineCommand::class);
}
}