сделан шаг 2

This commit is contained in:
Alisa
2026-06-03 23:24:10 +03:00
parent 6d7ef09c5b
commit 420e67c9bd
8 changed files with 688 additions and 10 deletions
+1
View File
@@ -12,3 +12,4 @@ DB_CHARSET=utf8mb4
MAIL_FROM=orders@loveriba.ru
ADMIN_EMAIL=
INSTALL_TOKEN=change-this-token
+371 -2
View File
@@ -1,2 +1,371 @@
-- RybStock database schema will be designed on step 2.
-- Database: alice_ro2
-- RybStock database schema
-- MariaDB/MySQL, charset utf8mb4
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS order_status_history;
DROP TABLE IF EXISTS order_items;
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS delivery_calculations;
DROP TABLE IF EXISTS promocode_targets;
DROP TABLE IF EXISTS promocodes;
DROP TABLE IF EXISTS product_search_terms;
DROP TABLE IF EXISTS product_images;
DROP TABLE IF EXISTS product_variants;
DROP TABLE IF EXISTS popular_products;
DROP TABLE IF EXISTS promo_block_items;
DROP TABLE IF EXISTS products;
DROP TABLE IF EXISTS categories;
DROP TABLE IF EXISTS reviews;
DROP TABLE IF EXISTS customer_login_codes;
DROP TABLE IF EXISTS customer_email_tokens;
DROP TABLE IF EXISTS site_settings;
DROP TABLE IF EXISTS holidays;
DROP TABLE IF EXISTS admins;
CREATE TABLE admins (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(120) NOT NULL,
email VARCHAR(190) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
role ENUM('owner', 'manager', 'content') NOT NULL DEFAULT 'owner',
is_active TINYINT(1) NOT NULL DEFAULT 1,
last_login_at DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_admins_email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE categories (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
parent_id BIGINT UNSIGNED NULL,
name VARCHAR(190) NOT NULL,
slug VARCHAR(220) NOT NULL,
description TEXT NULL,
seo_title VARCHAR(255) NULL,
seo_description TEXT NULL,
image_path VARCHAR(255) NULL,
sort_order INT NOT NULL DEFAULT 100,
is_active TINYINT(1) NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_categories_slug (slug),
KEY idx_categories_parent (parent_id),
KEY idx_categories_active_sort (is_active, sort_order),
CONSTRAINT fk_categories_parent
FOREIGN KEY (parent_id) REFERENCES categories(id)
ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE products (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
category_id BIGINT UNSIGNED NULL,
name VARCHAR(220) NOT NULL,
slug VARCHAR(240) NOT NULL,
sku VARCHAR(100) NULL,
short_description TEXT NULL,
description MEDIUMTEXT NULL,
product_info MEDIUMTEXT NULL,
main_image_path VARCHAR(255) NULL,
base_unit ENUM('kg', 'g', 'l', 'ml', 'pcs', 'pack') NOT NULL DEFAULT 'kg',
package_display_mode ENUM('auto', 'base_and_package', 'package_only') NOT NULL DEFAULT 'auto',
is_weight_product TINYINT(1) NOT NULL DEFAULT 1,
is_published TINYINT(1) NOT NULL DEFAULT 0,
is_available TINYINT(1) NOT NULL DEFAULT 1,
is_new TINYINT(1) NOT NULL DEFAULT 0,
is_hit TINYINT(1) NOT NULL DEFAULT 0,
is_promo TINYINT(1) NOT NULL DEFAULT 0,
sort_order INT NOT NULL DEFAULT 100,
sales_count INT UNSIGNED NOT NULL DEFAULT 0,
last_sold_at DATETIME NULL,
published_at DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_products_slug (slug),
UNIQUE KEY uq_products_sku (sku),
KEY idx_products_category (category_id),
KEY idx_products_public (is_published, is_available, sort_order),
KEY idx_products_sales (sales_count, last_sold_at),
FULLTEXT KEY ft_products_text (name, short_description, description, product_info),
CONSTRAINT fk_products_category
FOREIGN KEY (category_id) REFERENCES categories(id)
ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE product_variants (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
product_id BIGINT UNSIGNED NOT NULL,
name VARCHAR(190) NOT NULL,
unit ENUM('kg', 'g', 'l', 'ml', 'pcs', 'pack') NOT NULL DEFAULT 'kg',
package_quantity DECIMAL(10,3) NOT NULL DEFAULT 1.000,
step_quantity DECIMAL(10,3) NOT NULL DEFAULT 1.000,
price_per_unit DECIMAL(12,2) NULL,
package_price DECIMAL(12,2) NOT NULL,
old_package_price DECIMAL(12,2) NULL,
min_order_quantity DECIMAL(10,3) NOT NULL DEFAULT 1.000,
stock_quantity DECIMAL(12,3) NULL,
is_default TINYINT(1) NOT NULL DEFAULT 0,
is_published TINYINT(1) NOT NULL DEFAULT 1,
is_available TINYINT(1) NOT NULL DEFAULT 1,
sort_order INT NOT NULL DEFAULT 100,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_variants_product (product_id),
KEY idx_variants_public (product_id, is_published, is_available, sort_order),
CONSTRAINT fk_variants_product
FOREIGN KEY (product_id) REFERENCES products(id)
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE product_images (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
product_id BIGINT UNSIGNED NOT NULL,
path VARCHAR(255) NOT NULL,
alt VARCHAR(255) NULL,
sort_order INT NOT NULL DEFAULT 100,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_product_images_product (product_id, sort_order),
CONSTRAINT fk_product_images_product
FOREIGN KEY (product_id) REFERENCES products(id)
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE product_search_terms (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
product_id BIGINT UNSIGNED NOT NULL,
term VARCHAR(190) NOT NULL,
weight TINYINT UNSIGNED NOT NULL DEFAULT 5,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_search_terms_term (term),
KEY idx_search_terms_product (product_id),
CONSTRAINT fk_search_terms_product
FOREIGN KEY (product_id) REFERENCES products(id)
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE promocodes (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
code VARCHAR(80) NOT NULL,
title VARCHAR(190) NOT NULL,
discount_type ENUM('fixed', 'percent') NOT NULL,
discount_value DECIMAL(12,2) NOT NULL,
min_order_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00,
applies_to ENUM('order', 'category', 'product') NOT NULL DEFAULT 'order',
payment_note VARCHAR(255) NULL DEFAULT 'Действует при наличном расчете',
starts_at DATETIME NULL,
ends_at DATETIME NULL,
usage_limit INT UNSIGNED NULL,
used_count INT UNSIGNED NOT NULL DEFAULT 0,
is_active TINYINT(1) NOT NULL DEFAULT 1,
is_featured TINYINT(1) NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_promocodes_code (code),
KEY idx_promocodes_active (is_active, starts_at, ends_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE promocode_targets (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
promocode_id BIGINT UNSIGNED NOT NULL,
target_type ENUM('category', 'product') NOT NULL,
target_id BIGINT UNSIGNED NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_promocode_target (promocode_id, target_type, target_id),
KEY idx_promocode_targets_lookup (target_type, target_id),
CONSTRAINT fk_promocode_targets_promocode
FOREIGN KEY (promocode_id) REFERENCES promocodes(id)
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE delivery_calculations (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
delivery_type ENUM('inside_mkad', 'outside_mkad', 'pickup') NOT NULL,
address VARCHAR(500) NULL,
order_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00,
distance_from_mkad_km DECIMAL(8,2) NULL,
free_delivery_threshold DECIMAL(12,2) NULL,
delivery_price DECIMAL(12,2) NOT NULL DEFAULT 0.00,
lift_price DECIMAL(12,2) NOT NULL DEFAULT 0.00,
total_delivery_price DECIMAL(12,2) NOT NULL DEFAULT 0.00,
provider VARCHAR(80) NULL,
provider_payload MEDIUMTEXT NULL,
explanation TEXT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_delivery_type_created (delivery_type, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE orders (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
order_number VARCHAR(40) NOT NULL,
customer_name VARCHAR(120) NOT NULL,
phone VARCHAR(40) NOT NULL,
email VARCHAR(190) NULL,
delivery_type ENUM('inside_mkad', 'outside_mkad', 'pickup') NOT NULL,
address VARCHAR(500) NULL,
desired_date DATE NULL,
customer_comment TEXT NULL,
admin_comment TEXT NULL,
status ENUM('new', 'confirmed', 'assembling', 'ready', 'delivering', 'completed', 'cancelled') NOT NULL DEFAULT 'new',
subtotal_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00,
discount_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00,
delivery_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00,
total_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00,
promocode_id BIGINT UNSIGNED NULL,
promocode_code VARCHAR(80) NULL,
delivery_calculation_id BIGINT UNSIGNED NULL,
client_ip VARCHAR(45) NULL,
user_agent VARCHAR(255) NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_orders_number (order_number),
KEY idx_orders_status_created (status, created_at),
KEY idx_orders_email (email),
KEY idx_orders_phone (phone),
CONSTRAINT fk_orders_promocode
FOREIGN KEY (promocode_id) REFERENCES promocodes(id)
ON DELETE SET NULL,
CONSTRAINT fk_orders_delivery_calculation
FOREIGN KEY (delivery_calculation_id) REFERENCES delivery_calculations(id)
ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE order_items (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
order_id BIGINT UNSIGNED NOT NULL,
product_id BIGINT UNSIGNED NULL,
variant_id BIGINT UNSIGNED NULL,
product_name VARCHAR(220) NOT NULL,
variant_name VARCHAR(190) NOT NULL,
unit VARCHAR(20) NOT NULL,
package_quantity DECIMAL(10,3) NOT NULL,
quantity DECIMAL(10,3) NOT NULL,
price_per_unit DECIMAL(12,2) NULL,
package_price DECIMAL(12,2) NOT NULL,
line_total DECIMAL(12,2) NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_order_items_order (order_id),
KEY idx_order_items_product (product_id),
CONSTRAINT fk_order_items_order
FOREIGN KEY (order_id) REFERENCES orders(id)
ON DELETE CASCADE,
CONSTRAINT fk_order_items_product
FOREIGN KEY (product_id) REFERENCES products(id)
ON DELETE SET NULL,
CONSTRAINT fk_order_items_variant
FOREIGN KEY (variant_id) REFERENCES product_variants(id)
ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE order_status_history (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
order_id BIGINT UNSIGNED NOT NULL,
old_status VARCHAR(40) NULL,
new_status VARCHAR(40) NOT NULL,
comment TEXT NULL,
admin_id BIGINT UNSIGNED NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_status_history_order (order_id, created_at),
CONSTRAINT fk_status_history_order
FOREIGN KEY (order_id) REFERENCES orders(id)
ON DELETE CASCADE,
CONSTRAINT fk_status_history_admin
FOREIGN KEY (admin_id) REFERENCES admins(id)
ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE customer_login_codes (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
email VARCHAR(190) NOT NULL,
code_hash VARCHAR(255) NOT NULL,
expires_at DATETIME NOT NULL,
used_at DATETIME NULL,
attempts TINYINT UNSIGNED NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_customer_codes_email (email),
KEY idx_customer_codes_expires (expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE reviews (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
source ENUM('yandex', 'manual') NOT NULL DEFAULT 'manual',
author_name VARCHAR(160) NOT NULL,
rating TINYINT UNSIGNED NOT NULL DEFAULT 5,
text TEXT NOT NULL,
external_url VARCHAR(500) NULL,
reviewed_at DATE NULL,
is_published TINYINT(1) NOT NULL DEFAULT 1,
sort_order INT NOT NULL DEFAULT 100,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_reviews_public (is_published, rating, sort_order),
CONSTRAINT chk_reviews_rating CHECK (rating BETWEEN 1 AND 5)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE promo_block_items (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
product_id BIGINT UNSIGNED NOT NULL,
title VARCHAR(190) NULL,
is_active TINYINT(1) NOT NULL DEFAULT 1,
sort_order INT NOT NULL DEFAULT 100,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_promo_block_active (is_active, sort_order),
CONSTRAINT fk_promo_block_product
FOREIGN KEY (product_id) REFERENCES products(id)
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE popular_products (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
product_id BIGINT UNSIGNED NOT NULL,
mode ENUM('manual', 'pinned') NOT NULL DEFAULT 'manual',
is_active TINYINT(1) NOT NULL DEFAULT 1,
sort_order INT NOT NULL DEFAULT 100,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_popular_product (product_id),
KEY idx_popular_products_active (is_active, sort_order),
CONSTRAINT fk_popular_product
FOREIGN KEY (product_id) REFERENCES products(id)
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE site_settings (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
setting_key VARCHAR(120) NOT NULL,
setting_value MEDIUMTEXT NULL,
value_type ENUM('string', 'text', 'number', 'bool', 'json') NOT NULL DEFAULT 'string',
group_name VARCHAR(80) NOT NULL DEFAULT 'general',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_site_settings_key (setting_key),
KEY idx_site_settings_group (group_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE holidays (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
holiday_date DATE NOT NULL,
title VARCHAR(190) NOT NULL,
is_working_day TINYINT(1) NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_holidays_date (holiday_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
SET FOREIGN_KEY_CHECKS = 1;
+37
View File
@@ -0,0 +1,37 @@
-- RybStock starter data
-- Run after database/schema.sql
SET NAMES utf8mb4;
INSERT INTO categories (name, slug, sort_order, is_active) VALUES
('Рыба', 'fish', 10, 1),
('Готовая продукция', 'gotovaya-produkciya', 20, 1),
('Полуфабрикаты', 'polufabrikaty', 30, 1),
('Мясо', 'myaso', 40, 1),
('Сыры', 'syry', 50, 1),
('Ягоды', 'yagody', 60, 1),
('Овощи', 'ovoschi', 70, 1);
INSERT INTO promocodes
(code, title, discount_type, discount_value, min_order_amount, applies_to, is_active, is_featured)
VALUES
('Рыбсток7000', '300 руб. при заказе от 7000 руб.', 'fixed', 300.00, 7000.00, 'order', 1, 1),
('Рыбсток20000', '5% при заказе от 20000 руб.', 'percent', 5.00, 20000.00, 'order', 1, 1),
('Рыбсток50000', '7% при заказе от 50000 руб.', 'percent', 7.00, 50000.00, 'order', 1, 1);
INSERT INTO site_settings (setting_key, setting_value, value_type, group_name) VALUES
('site_phone', '+7-977-736-33-63', 'string', 'contacts'),
('admin_email', '', 'string', 'mail'),
('inside_mkad_free_from', '5000', 'number', 'delivery'),
('inside_mkad_paid_price', '599', 'number', 'delivery'),
('outside_mkad_base_price', '199', 'number', 'delivery'),
('outside_mkad_km_price', '60', 'number', 'delivery'),
('outside_mkad_free_km_multiplier', '1000', 'number', 'delivery'),
('outside_mkad_free_under_10km', '10000', 'number', 'delivery'),
('lift_price', '390', 'number', 'delivery'),
('freezer_temp_min', '-20', 'number', 'freshness'),
('freezer_temp_max', '-18', 'number', 'freshness'),
('fridge_temp_min', '-2', 'number', 'freshness'),
('fridge_temp_max', '1', 'number', 'freshness'),
('freezer_area_text', 'более 1800 кв. м', 'string', 'freshness'),
('fridge_area_text', 'более 500 кв. м', 'string', 'freshness');
+160
View File
@@ -0,0 +1,160 @@
# Шаг 2. База данных и связи
## Что сделано
Создана SQL-схема `database/schema.sql` для MariaDB/MySQL и стартовые данные `database/seed.sql`.
Также создана локальная страница установки `public/install.php`.
Она применяет:
- `database/schema.sql`;
- `database/seed.sql`.
Важно: установка пересоздает таблицы, поэтому использовать ее только на старте или в локальной разработке.
Схема покрывает основные части проекта:
- администраторы;
- категории и подкатегории;
- товары;
- фасовки и цены;
- изображения товаров;
- поисковые термины;
- промокоды и их привязки;
- расчеты доставки;
- заказы и позиции заказов;
- история статусов заказа;
- вход клиента по email-коду;
- отзывы;
- акции;
- популярные товары;
- настройки сайта;
- праздничные/нерабочие дни.
## Главная логика связей
### Категории
Таблица `categories` поддерживает вложенность через `parent_id`.
Это позволяет хранить:
- главные категории;
- подкатегории;
- меню каталога;
- SEO-текст для страниц категорий.
### Товары
Таблица `products` хранит основную карточку товара:
- название;
- slug;
- категорию;
- описание;
- основное фото;
- базовую единицу;
- режим отображения цены;
- публикацию;
- доступность;
- признаки "новинка", "хит", "акция".
### Фасовки
Таблица `product_variants` хранит отдельные фасовки товара.
Именно фасовка попадает в корзину, а не просто товар.
Пример:
- товар: горбуша неразделанная;
- фасовка: пласт 10 кг;
- `package_quantity`: 10;
- `step_quantity`: 10;
- `price_per_unit`: 600;
- `package_price`: 6000.
Так можно корректно сделать плюс/минус кратно фасовке.
### Заказы
Таблица `orders` хранит заказ целиком:
- имя;
- телефон;
- email;
- тип получения;
- адрес;
- суммы;
- промокод;
- расчет доставки;
- статус.
Таблица `order_items` хранит снимок каждой позиции заказа.
Снимок нужен, чтобы старые заказы не менялись при изменении цены или названия товара в каталоге.
### Промокоды
Таблица `promocodes` хранит сам промокод.
Таблица `promocode_targets` хранит ограничения:
- на категорию;
- на товар.
Если промокод действует на весь заказ, targets не нужны.
### Доставка
Таблица `delivery_calculations` хранит результат расчета:
- тип доставки;
- адрес;
- сумма заказа;
- расстояние от МКАД;
- порог бесплатной доставки;
- стоимость доставки;
- стоимость подъема;
- объяснение расчета.
Это нужно, чтобы в заказе сохранялся расчет, который видел клиент.
### История заказов по email
Таблица `customer_login_codes` хранит одноразовые коды входа по email.
SMS не используются.
### Настройки
Таблица `site_settings` хранит настройки сайта:
- телефон;
- email администратора;
- правила доставки;
- температуры камер;
- тексты и числовые параметры.
## Как применить схему
1. Открыть в браузере `/install.php?token=rybstock-local-install`.
2. Нажать кнопку "Создать таблицы и стартовые данные".
3. После успешной установки база будет содержать таблицы проекта и стартовые категории/настройки.
Страница работает только при `APP_ENV=local` и правильном `INSTALL_TOKEN` из `.env`.
## Что нужно сделать следующим шагом
Шаг 3:
1. Создать базовые PHP-классы для работы с таблицами:
- `CategoryRepository`;
- `ProductRepository`;
- `ProductVariantRepository`;
- `OrderRepository`;
- `PromocodeRepository`;
- `SettingRepository`.
2. Сделать первый вывод категорий на главной странице.
3. Подготовить основу админки: вход администратора и список разделов.
+27
View File
@@ -51,3 +51,30 @@ p {
border-radius: 8px;
background: #eef4ea;
}
.status-box.success {
background: #e8f5ec;
color: #145a2a;
}
.status-box.danger {
background: #fff0ee;
color: #8a1f11;
}
.primary-button {
appearance: none;
border: 0;
border-radius: 8px;
padding: 14px 18px;
margin-top: 18px;
font-size: 16px;
font-weight: 700;
color: #ffffff;
background: #1d5d3b;
cursor: pointer;
}
.primary-button:hover {
background: #16492e;
}
+85
View File
@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
use App\Core\Database;
require_once dirname(__DIR__) . '/app/bootstrap.php';
$isLocal = app_env('APP_ENV', 'production') === 'local';
$expectedToken = app_env('INSTALL_TOKEN', '');
$actualToken = $_GET['token'] ?? '';
$canRun = $isLocal && $expectedToken !== '' && hash_equals($expectedToken, (string) $actualToken);
$result = null;
$error = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!$canRun) {
http_response_code(403);
$error = 'Доступ запрещен. Проверьте APP_ENV и INSTALL_TOKEN.';
} else {
try {
$pdo = Database::pdo();
run_sql_file($pdo, base_path('database/schema.sql'));
run_sql_file($pdo, base_path('database/seed.sql'));
$result = 'База данных установлена. Таблицы и стартовые данные созданы.';
} catch (Throwable $exception) {
http_response_code(500);
$error = app_env('APP_DEBUG', 'false') === 'true'
? $exception->getMessage()
: 'Ошибка установки базы данных.';
}
}
}
function run_sql_file(PDO $pdo, string $path): void
{
if (!is_file($path)) {
throw new RuntimeException('SQL file not found: ' . $path);
}
$sql = file_get_contents($path);
if ($sql === false) {
throw new RuntimeException('Cannot read SQL file: ' . $path);
}
$pdo->exec($sql);
}
?>
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Установка базы Рыбсток</title>
<link rel="stylesheet" href="/assets/css/app.css">
</head>
<body>
<main class="page">
<section class="start-screen">
<p class="eyebrow">Шаг 2</p>
<h1>Установка базы</h1>
<p>Эта страница применяет `database/schema.sql` и `database/seed.sql`. Таблицы будут пересозданы.</p>
<?php if (!$isLocal): ?>
<div class="status-box danger">Установка доступна только при APP_ENV=local.</div>
<?php elseif (!$canRun): ?>
<div class="status-box danger">Добавьте токен в адрес страницы: <strong>?token=...</strong></div>
<?php else: ?>
<form method="post">
<button class="primary-button" type="submit">Создать таблицы и стартовые данные</button>
</form>
<?php endif; ?>
<?php if ($result !== null): ?>
<div class="status-box success"><?= htmlspecialchars($result, ENT_QUOTES, 'UTF-8') ?></div>
<?php endif; ?>
<?php if ($error !== null): ?>
<div class="status-box danger"><?= htmlspecialchars($error, ENT_QUOTES, 'UTF-8') ?></div>
<?php endif; ?>
</section>
</main>
</body>
</html>
+2 -1
View File
@@ -628,4 +628,5 @@
## 11. Статус выполнения
- Шаг 1 выполнен: выбрана техническая связка на чистом PHP, создан стартовый каркас проекта, подключение к MariaDB через `.env`, базовые папки и файл `ШАГ_1_ТЕХНИЧЕСКОЕ_РЕШЕНИЕ.md`.
- Следующий шаг: спроектировать базу данных, модели, связи и структуру собственной админки.
- Шаг 2 выполнен: создана SQL-схема базы `database/schema.sql`, стартовые данные `database/seed.sql`, страница установки `public/install.php` и документ `docs/ШАГ_2_БАЗА_ДАННЫХ.md`.
- Следующий шаг: применить схему через `/install.php?token=rybstock-local-install`, затем сделать первые PHP-классы для чтения категорий, товаров, фасовок и настроек.
@@ -251,21 +251,19 @@ loveriba.ru/
## Второй шаг
Следующий шаг: спроектировать базу данных и модели проекта под чистый PHP.
Второй шаг выполнен: база данных спроектирована в файле `database/schema.sql`, стартовые данные добавлены в `database/seed.sql`, описание связей создано в `docs/ШАГ_2_БАЗА_ДАННЫХ.md`.
Нужно подготовить:
Подготовлено:
1. Список таблиц.
2. Поля каждой таблицы.
3. Связи между таблицами.
4. Правила расчета цены фасовки.
5. Правила расчета доставки.
6. Структуру собственной админки.
7. Первичный SQL-файл `database/schema.sql`.
6. Первичный SQL-файл `database/schema.sql`.
7. Стартовые данные `database/seed.sql`.
После второго шага можно будет создавать файловую структуру проекта и начинать код.
Файловая структура уже создана на шаге 1, поэтому на шаге 2 можно сразу перейти к SQL-схеме, таблицам и первичным PHP-моделям.
Следующий шаг: сделать применение SQL-схемы и первичные PHP-классы для чтения данных.
## Что нужно уточнить перед кодом