сделан шаг 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 MAIL_FROM=orders@loveriba.ru
ADMIN_EMAIL= ADMIN_EMAIL=
INSTALL_TOKEN=change-this-token
+371 -2
View File
@@ -1,2 +1,371 @@
-- RybStock database schema will be designed on step 2. -- RybStock database schema
-- Database: alice_ro2 -- 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; border-radius: 8px;
background: #eef4ea; 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. Статус выполнения ## 11. Статус выполнения
- Шаг 1 выполнен: выбрана техническая связка на чистом PHP, создан стартовый каркас проекта, подключение к MariaDB через `.env`, базовые папки и файл `ШАГ_1_ТЕХНИЧЕСКОЕ_РЕШЕНИЕ.md`. - Шаг 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. Список таблиц. 1. Список таблиц.
2. Поля каждой таблицы. 2. Поля каждой таблицы.
3. Связи между таблицами. 3. Связи между таблицами.
4. Правила расчета цены фасовки. 4. Правила расчета цены фасовки.
5. Правила расчета доставки. 5. Правила расчета доставки.
6. Структуру собственной админки. 6. Первичный SQL-файл `database/schema.sql`.
7. Первичный SQL-файл `database/schema.sql`. 7. Стартовые данные `database/seed.sql`.
После второго шага можно будет создавать файловую структуру проекта и начинать код. Следующий шаг: сделать применение SQL-схемы и первичные PHP-классы для чтения данных.
Файловая структура уже создана на шаге 1, поэтому на шаге 2 можно сразу перейти к SQL-схеме, таблицам и первичным PHP-моделям.
## Что нужно уточнить перед кодом ## Что нужно уточнить перед кодом