Fix LESS import error and refactor project structure

This commit is contained in:
kirill.khorkov
2026-01-03 18:59:56 +03:00
parent 1bb0fc02e6
commit 4a8d4f8c3f
201 changed files with 891 additions and 14311 deletions

View File

@@ -4,17 +4,11 @@ namespace App\Models;
use App\Core\Model;
/**
* Cart - модель корзины
*/
class Cart extends Model
{
protected string $table = 'cart';
protected string $primaryKey = 'cart_id';
/**
* Получить корзину пользователя
*/
public function getUserCart(int $userId): array
{
$sql = "SELECT
@@ -33,9 +27,6 @@ class Cart extends Model
return $this->query($sql, [$userId]);
}
/**
* Получить общую сумму корзины
*/
public function getCartTotal(int $userId): array
{
$sql = "SELECT
@@ -52,9 +43,6 @@ class Cart extends Model
];
}
/**
* Получить количество товаров в корзине
*/
public function getCount(int $userId): int
{
$sql = "SELECT COALESCE(SUM(quantity), 0) as total FROM {$this->table} WHERE user_id = ?";
@@ -62,19 +50,14 @@ class Cart extends Model
return (int) $result['total'];
}
/**
* Добавить товар в корзину
*/
public function addItem(int $userId, int $productId, int $quantity = 1): bool
{
// Проверяем, есть ли уже этот товар в корзине
$existing = $this->findWhere([
'user_id' => $userId,
'product_id' => $productId
]);
if ($existing) {
// Увеличиваем количество
$newQuantity = $existing['quantity'] + $quantity;
return $this->update($existing['cart_id'], [
'quantity' => $newQuantity,
@@ -82,7 +65,6 @@ class Cart extends Model
]);
}
// Добавляем новую запись
return $this->create([
'user_id' => $userId,
'product_id' => $productId,
@@ -90,9 +72,6 @@ class Cart extends Model
]) !== null;
}
/**
* Обновить количество товара в корзине
*/
public function updateQuantity(int $userId, int $productId, int $quantity): bool
{
$sql = "UPDATE {$this->table}
@@ -101,27 +80,18 @@ class Cart extends Model
return $this->execute($sql, [$quantity, $userId, $productId]);
}
/**
* Удалить товар из корзины
*/
public function removeItem(int $userId, int $productId): bool
{
$sql = "DELETE FROM {$this->table} WHERE user_id = ? AND product_id = ?";
return $this->execute($sql, [$userId, $productId]);
}
/**
* Очистить корзину пользователя
*/
public function clearCart(int $userId): bool
{
$sql = "DELETE FROM {$this->table} WHERE user_id = ?";
return $this->execute($sql, [$userId]);
}
/**
* Проверить, есть ли товар в корзине
*/
public function hasItem(int $userId, int $productId): bool
{
$item = $this->findWhere([
@@ -131,9 +101,6 @@ class Cart extends Model
return $item !== null;
}
/**
* Получить товар из корзины
*/
public function getItem(int $userId, int $productId): ?array
{
return $this->findWhere([
@@ -142,4 +109,3 @@ class Cart extends Model
]);
}
}

View File

@@ -4,17 +4,11 @@ namespace App\Models;
use App\Core\Model;
/**
* Category - модель категории товаров
*/
class Category extends Model
{
protected string $table = 'categories';
protected string $primaryKey = 'category_id';
/**
* Получить активные категории
*/
public function getActive(): array
{
$sql = "SELECT * FROM {$this->table}
@@ -23,9 +17,6 @@ class Category extends Model
return $this->query($sql);
}
/**
* Получить родительские категории (без parent_id)
*/
public function getParent(): array
{
$sql = "SELECT * FROM {$this->table}
@@ -34,9 +25,6 @@ class Category extends Model
return $this->query($sql);
}
/**
* Получить подкатегории
*/
public function getChildren(int $parentId): array
{
$sql = "SELECT * FROM {$this->table}
@@ -45,17 +33,11 @@ class Category extends Model
return $this->query($sql, [$parentId]);
}
/**
* Получить категорию по slug
*/
public function findBySlug(string $slug): ?array
{
return $this->findWhere(['slug' => $slug]);
}
/**
* Получить все категории с количеством товаров
*/
public function getAllWithProductCount(): array
{
$sql = "SELECT c1.*, c2.name as parent_name,
@@ -66,9 +48,6 @@ class Category extends Model
return $this->query($sql);
}
/**
* Создать категорию
*/
public function createCategory(array $data): ?int
{
$slug = $this->generateSlug($data['name']);
@@ -83,9 +62,6 @@ class Category extends Model
]);
}
/**
* Обновить категорию
*/
public function updateCategory(int $id, array $data): bool
{
$updateData = [
@@ -101,23 +77,17 @@ class Category extends Model
return $this->update($id, $updateData);
}
/**
* Безопасное удаление категории
*/
public function safeDelete(int $id): array
{
// Проверяем наличие товаров
$sql = "SELECT COUNT(*) as cnt FROM products WHERE category_id = ?";
$result = $this->queryOne($sql, [$id]);
$productCount = (int) $result['cnt'];
// Проверяем наличие дочерних категорий
$sql = "SELECT COUNT(*) as cnt FROM {$this->table} WHERE parent_id = ?";
$result = $this->queryOne($sql, [$id]);
$childCount = (int) $result['cnt'];
if ($productCount > 0 || $childCount > 0) {
// Скрываем вместо удаления
$this->update($id, ['is_active' => false]);
return [
'deleted' => false,
@@ -126,19 +96,14 @@ class Category extends Model
];
}
// Удаляем полностью
$this->delete($id);
return ['deleted' => true, 'hidden' => false];
}
/**
* Генерация slug из названия
*/
private function generateSlug(string $name): string
{
$slug = mb_strtolower($name);
// Транслитерация
$transliteration = [
'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd',
'е' => 'e', 'ё' => 'yo', 'ж' => 'zh', 'з' => 'z', 'и' => 'i',
@@ -156,4 +121,3 @@ class Category extends Model
return $slug;
}
}

View File

@@ -5,17 +5,11 @@ namespace App\Models;
use App\Core\Model;
use App\Core\Database;
/**
* Order - модель заказа
*/
class Order extends Model
{
protected string $table = 'orders';
protected string $primaryKey = 'order_id';
/**
* Создать заказ из корзины
*/
public function createFromCart(int $userId, array $cartItems, array $orderData): ?array
{
$db = Database::getInstance();
@@ -23,7 +17,6 @@ class Order extends Model
try {
$db->beginTransaction();
// Считаем итоговую сумму
$subtotal = 0;
foreach ($cartItems as $item) {
$subtotal += $item['price'] * $item['quantity'];
@@ -33,10 +26,8 @@ class Order extends Model
$deliveryPrice = (float) ($orderData['delivery_price'] ?? 2000);
$finalAmount = $subtotal - $discountAmount + $deliveryPrice;
// Генерируем номер заказа
$orderNumber = 'ORD-' . date('Ymd-His') . '-' . rand(1000, 9999);
// Создаем заказ
$orderId = $this->create([
'user_id' => $userId,
'order_number' => $orderNumber,
@@ -61,18 +52,15 @@ class Order extends Model
throw new \Exception('Не удалось создать заказ');
}
// Добавляем товары в заказ
foreach ($cartItems as $item) {
$this->addOrderItem($orderId, $item);
}
// Уменьшаем количество товаров на складе
$productModel = new Product();
foreach ($cartItems as $item) {
$productModel->decreaseStock($item['product_id'], $item['quantity']);
}
// Очищаем корзину
$cartModel = new Cart();
$cartModel->clearCart($userId);
@@ -90,9 +78,6 @@ class Order extends Model
}
}
/**
* Добавить товар в заказ
*/
private function addOrderItem(int $orderId, array $item): void
{
$sql = "INSERT INTO order_items
@@ -111,9 +96,6 @@ class Order extends Model
]);
}
/**
* Получить заказ с подробностями
*/
public function getWithDetails(int $orderId): ?array
{
$sql = "SELECT o.*, u.email as user_email, u.full_name as user_full_name
@@ -130,9 +112,6 @@ class Order extends Model
return $order;
}
/**
* Получить товары заказа
*/
public function getOrderItems(int $orderId): array
{
$sql = "SELECT oi.*, p.image_url
@@ -142,9 +121,6 @@ class Order extends Model
return $this->query($sql, [$orderId]);
}
/**
* Получить заказы пользователя
*/
public function getUserOrders(int $userId, int $limit = 50): array
{
$sql = "SELECT * FROM {$this->table}
@@ -154,9 +130,6 @@ class Order extends Model
return $this->query($sql, [$userId, $limit]);
}
/**
* Получить все заказы для админки
*/
public function getAllForAdmin(int $limit = 50): array
{
$sql = "SELECT o.*, u.email as user_email
@@ -167,9 +140,6 @@ class Order extends Model
return $this->query($sql, [$limit]);
}
/**
* Обновить статус заказа
*/
public function updateStatus(int $orderId, string $status): bool
{
$updateData = [
@@ -184,25 +154,19 @@ class Order extends Model
return $this->update($orderId, $updateData);
}
/**
* Получить статистику заказов
*/
public function getStats(): array
{
$stats = [];
// Общее количество заказов
$result = $this->queryOne("SELECT COUNT(*) as cnt FROM {$this->table}");
$stats['total'] = (int) $result['cnt'];
// Выручка
$result = $this->queryOne(
"SELECT COALESCE(SUM(final_amount), 0) as revenue
FROM {$this->table} WHERE status = 'completed'"
);
$stats['revenue'] = (float) $result['revenue'];
// По статусам
$statuses = $this->query(
"SELECT status, COUNT(*) as cnt FROM {$this->table} GROUP BY status"
);
@@ -214,4 +178,3 @@ class Order extends Model
return $stats;
}
}

View File

@@ -4,17 +4,11 @@ namespace App\Models;
use App\Core\Model;
/**
* Product - модель товара
*/
class Product extends Model
{
protected string $table = 'products';
protected string $primaryKey = 'product_id';
/**
* Получить товар с категорией
*/
public function findWithCategory(int $id): ?array
{
$sql = "SELECT p.*, c.name as category_name, c.slug as category_slug
@@ -24,9 +18,6 @@ class Product extends Model
return $this->queryOne($sql, [$id]);
}
/**
* Получить доступные товары
*/
public function getAvailable(array $filters = [], int $limit = 50): array
{
$sql = "SELECT p.*, c.name as category_name
@@ -35,13 +26,11 @@ class Product extends Model
WHERE p.is_available = TRUE";
$params = [];
// Фильтр по категории
if (!empty($filters['category_id'])) {
$sql .= " AND p.category_id = ?";
$params[] = $filters['category_id'];
}
// Фильтр по цене
if (!empty($filters['min_price'])) {
$sql .= " AND p.price >= ?";
$params[] = $filters['min_price'];
@@ -51,21 +40,18 @@ class Product extends Model
$params[] = $filters['max_price'];
}
// Фильтр по цветам
if (!empty($filters['colors']) && is_array($filters['colors'])) {
$placeholders = implode(',', array_fill(0, count($filters['colors']), '?'));
$sql .= " AND p.color IN ({$placeholders})";
$params = array_merge($params, $filters['colors']);
}
// Фильтр по материалам
if (!empty($filters['materials']) && is_array($filters['materials'])) {
$placeholders = implode(',', array_fill(0, count($filters['materials']), '?'));
$sql .= " AND p.material IN ({$placeholders})";
$params = array_merge($params, $filters['materials']);
}
// Поиск по названию/описанию
if (!empty($filters['search'])) {
$sql .= " AND (p.name ILIKE ? OR p.description ILIKE ?)";
$search = '%' . $filters['search'] . '%';
@@ -79,9 +65,6 @@ class Product extends Model
return $this->query($sql, $params);
}
/**
* Получить все товары (включая недоступные) для админки
*/
public function getAllForAdmin(bool $showAll = true): array
{
$sql = "SELECT p.*, c.name as category_name
@@ -97,9 +80,6 @@ class Product extends Model
return $this->query($sql);
}
/**
* Получить похожие товары
*/
public function getSimilar(int $productId, int $categoryId, int $limit = 3): array
{
$sql = "SELECT * FROM {$this->table}
@@ -111,9 +91,6 @@ class Product extends Model
return $this->query($sql, [$categoryId, $productId, $limit]);
}
/**
* Получить доступные цвета
*/
public function getAvailableColors(): array
{
$sql = "SELECT DISTINCT color FROM {$this->table}
@@ -123,9 +100,6 @@ class Product extends Model
return array_column($result, 'color');
}
/**
* Получить доступные материалы
*/
public function getAvailableMaterials(): array
{
$sql = "SELECT DISTINCT material FROM {$this->table}
@@ -135,9 +109,6 @@ class Product extends Model
return array_column($result, 'material');
}
/**
* Создать товар
*/
public function createProduct(array $data): ?int
{
$slug = $this->generateSlug($data['name']);
@@ -161,9 +132,6 @@ class Product extends Model
]);
}
/**
* Обновить товар
*/
public function updateProduct(int $id, array $data): bool
{
$updateData = [
@@ -183,9 +151,6 @@ class Product extends Model
return $this->update($id, $updateData);
}
/**
* Уменьшить количество товара на складе
*/
public function decreaseStock(int $productId, int $quantity): bool
{
$sql = "UPDATE {$this->table}
@@ -195,18 +160,12 @@ class Product extends Model
return $this->execute($sql, [$quantity, $productId]);
}
/**
* Проверить наличие товара
*/
public function checkStock(int $productId, int $quantity): bool
{
$product = $this->find($productId);
return $product && $product['is_available'] && $product['stock_quantity'] >= $quantity;
}
/**
* Генерация slug
*/
private function generateSlug(string $name): string
{
$slug = mb_strtolower($name);
@@ -227,13 +186,9 @@ class Product extends Model
return trim($slug, '-');
}
/**
* Генерация артикула
*/
private function generateSku(string $name): string
{
$prefix = strtoupper(substr(preg_replace('/[^a-zA-Z0-9]/', '', $name), 0, 6));
return 'PROD-' . $prefix . '-' . rand(100, 999);
}
}

View File

@@ -4,46 +4,30 @@ namespace App\Models;
use App\Core\Model;
/**
* User - модель пользователя
*/
class User extends Model
{
protected string $table = 'users';
protected string $primaryKey = 'user_id';
/**
* Найти пользователя по email
*/
public function findByEmail(string $email): ?array
{
return $this->findWhere(['email' => $email]);
}
/**
* Проверить пароль пользователя
*/
public function verifyPassword(string $password, string $hash): bool
{
return password_verify($password, $hash);
}
/**
* Хешировать пароль
*/
public function hashPassword(string $password): string
{
return password_hash($password, PASSWORD_DEFAULT);
}
/**
* Создать нового пользователя
*/
public function register(array $data): ?int
{
$config = require dirname(__DIR__, 2) . '/config/app.php';
// Проверяем, является ли email администраторским
$isAdmin = in_array(strtolower($data['email']), $config['admin_emails'] ?? []);
return $this->create([
@@ -52,14 +36,11 @@ class User extends Model
'full_name' => $data['full_name'],
'phone' => $data['phone'] ?? null,
'city' => $data['city'] ?? null,
'is_admin' => $isAdmin,
'is_active' => true
'is_admin' => $isAdmin ? 'true' : 'false',
'is_active' => 'true'
]);
}
/**
* Авторизация пользователя
*/
public function authenticate(string $email, string $password): ?array
{
$user = $this->findByEmail($email);
@@ -76,7 +57,6 @@ class User extends Model
return null;
}
// Обновляем время последнего входа
$this->update($user['user_id'], [
'last_login' => date('Y-m-d H:i:s')
]);
@@ -84,9 +64,6 @@ class User extends Model
return $user;
}
/**
* Получить активных пользователей
*/
public function getActive(int $limit = 50): array
{
$sql = "SELECT * FROM {$this->table}
@@ -96,9 +73,6 @@ class User extends Model
return $this->query($sql, [$limit]);
}
/**
* Получить всех пользователей с пагинацией
*/
public function getAllPaginated(int $limit = 50, int $offset = 0): array
{
$sql = "SELECT * FROM {$this->table}
@@ -107,18 +81,12 @@ class User extends Model
return $this->query($sql, [$limit, $offset]);
}
/**
* Проверить существование email
*/
public function emailExists(string $email): bool
{
$user = $this->findByEmail($email);
return $user !== null;
}
/**
* Обновить профиль пользователя
*/
public function updateProfile(int $userId, array $data): bool
{
$allowedFields = ['full_name', 'phone', 'city'];
@@ -128,9 +96,6 @@ class User extends Model
return $this->update($userId, $updateData);
}
/**
* Изменить пароль
*/
public function changePassword(int $userId, string $newPassword): bool
{
return $this->update($userId, [
@@ -139,9 +104,6 @@ class User extends Model
]);
}
/**
* Заблокировать/разблокировать пользователя
*/
public function setActive(int $userId, bool $active): bool
{
return $this->update($userId, [
@@ -150,4 +112,3 @@ class User extends Model
]);
}
}