Initial commit
Some checks failed
Deploy / deploy (push) Has been cancelled

This commit is contained in:
ssww23
2026-03-10 00:55:37 +03:00
parent fc0f28d830
commit 93a655235a
155 changed files with 24768 additions and 0 deletions

35
app/Models/Category.php Normal file
View File

@@ -0,0 +1,35 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
use HasFactory;
protected $fillable = [
'name',
'slug',
'description',
'is_active',
];
protected function casts(): array
{
return [
'is_active' => 'boolean',
];
}
public function products()
{
return $this->hasMany(Product::class);
}
public function getRouteKeyName(): string
{
return 'slug';
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class ChatConversation extends Model
{
use HasFactory;
public const STATUS_OPEN = 'open';
public const STATUS_CLOSED = 'closed';
protected $fillable = [
'user_id',
'visitor_token',
'status',
'last_message_at',
];
protected function casts(): array
{
return [
'last_message_at' => 'datetime',
];
}
public function user()
{
return $this->belongsTo(User::class);
}
public function messages()
{
return $this->hasMany(ChatMessage::class);
}
public function latestMessage()
{
return $this->hasOne(ChatMessage::class)->latestOfMany();
}
public function getDisplayNameAttribute(): string
{
if ($this->user?->name) {
return $this->user->name;
}
return 'Гость #' . $this->id;
}
public function isClosed(): bool
{
return $this->status === self::STATUS_CLOSED;
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class ChatMessage extends Model
{
use HasFactory;
protected $fillable = [
'chat_conversation_id',
'sender',
'body',
'is_read',
];
protected function casts(): array
{
return [
'is_read' => 'boolean',
];
}
public function conversation()
{
return $this->belongsTo(ChatConversation::class, 'chat_conversation_id');
}
}

69
app/Models/HomeSlide.php Normal file
View File

@@ -0,0 +1,69 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class HomeSlide extends Model
{
use HasFactory;
protected $fillable = [
'zone',
'title',
'show_title',
'subtitle',
'show_subtitle',
'button_text',
'button_url',
'show_button',
'image_path',
'sort_order',
'is_active',
];
protected function casts(): array
{
return [
'show_title' => 'boolean',
'show_subtitle' => 'boolean',
'show_button' => 'boolean',
'sort_order' => 'integer',
'is_active' => 'boolean',
];
}
public function scopeActive($query)
{
return $query->where('is_active', true);
}
public function scopeForZone($query, string $zone)
{
return $query->where('zone', $zone);
}
public function scopeOrdered($query)
{
return $query->orderBy('sort_order')->orderBy('id');
}
public function getImageUrlAttribute(): ?string
{
if (!$this->image_path) {
return null;
}
if (Str::startsWith($this->image_path, ['http://', 'https://', '/'])) {
return $this->image_path;
}
if (Str::startsWith($this->image_path, 'uploads/')) {
return asset($this->image_path);
}
return '/storage/' . ltrim($this->image_path, '/');
}
}

50
app/Models/Order.php Normal file
View File

@@ -0,0 +1,50 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
use HasFactory;
protected $fillable = [
'user_id',
'status',
'payment_method',
'total',
'items_count',
'customer_name',
'email',
'phone',
'address',
'comment',
];
protected function casts(): array
{
return [
'total' => 'decimal:2',
'items_count' => 'integer',
];
}
public function user()
{
return $this->belongsTo(User::class);
}
public function items()
{
return $this->hasMany(OrderItem::class);
}
public function getPaymentMethodLabelAttribute(): string
{
return match ($this->payment_method) {
'card_transfer' => 'Перевод по реквизитам (на карту)',
default => 'Не указан',
};
}
}

39
app/Models/OrderItem.php Normal file
View File

@@ -0,0 +1,39 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class OrderItem extends Model
{
use HasFactory;
protected $fillable = [
'order_id',
'product_id',
'name',
'price',
'quantity',
'subtotal',
];
protected function casts(): array
{
return [
'price' => 'decimal:2',
'subtotal' => 'decimal:2',
'quantity' => 'integer',
];
}
public function order()
{
return $this->belongsTo(Order::class);
}
public function product()
{
return $this->belongsTo(Product::class);
}
}

89
app/Models/Product.php Normal file
View File

@@ -0,0 +1,89 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class Product extends Model
{
use HasFactory;
protected $fillable = [
'category_id',
'name',
'slug',
'sku',
'price',
'old_price',
'stock',
'short_description',
'description',
'image_path',
'gallery_paths',
'specs',
'is_active',
];
protected function casts(): array
{
return [
'price' => 'decimal:2',
'old_price' => 'decimal:2',
'stock' => 'integer',
'gallery_paths' => 'array',
'specs' => 'array',
'is_active' => 'boolean',
];
}
public function category()
{
return $this->belongsTo(Category::class);
}
public function getRouteKeyName(): string
{
return 'slug';
}
public function getImageUrlAttribute(): ?string
{
return $this->resolveImagePath($this->image_path);
}
public function getGalleryUrlsAttribute(): array
{
$paths = collect((array) ($this->gallery_paths ?? []))
->prepend($this->image_path)
->filter(fn ($path) => is_string($path) && trim($path) !== '')
->map(fn (string $path) => trim($path))
->unique()
->values();
return $paths
->map(fn (string $path) => $this->resolveImagePath($path))
->filter()
->values()
->all();
}
private function resolveImagePath(?string $path): ?string
{
$path = trim((string) $path);
if ($path === '') {
return null;
}
if (Str::startsWith($path, ['http://', 'https://', '/'])) {
return $path;
}
if (Str::startsWith($path, 'uploads/')) {
return asset($path);
}
return '/storage/' . ltrim($path, '/');
}
}

60
app/Models/User.php Normal file
View File

@@ -0,0 +1,60 @@
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'name',
'email',
'password',
'is_admin',
];
/**
* The attributes that should be hidden for serialization.
*
* @var list<string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
'is_admin' => 'boolean',
];
}
public function orders()
{
return $this->hasMany(Order::class);
}
public function chatConversations()
{
return $this->hasMany(ChatConversation::class);
}
}