сделан шаг 2
This commit is contained in:
@@ -12,3 +12,4 @@ DB_CHARSET=utf8mb4
|
||||
|
||||
MAIL_FROM=orders@loveriba.ru
|
||||
ADMIN_EMAIL=
|
||||
INSTALL_TOKEN=change-this-token
|
||||
|
||||
+371
-2
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
@@ -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. Подготовить основу админки: вход администратора и список разделов.
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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-классы для чтения данных.
|
||||
|
||||
## Что нужно уточнить перед кодом
|
||||
|
||||
|
||||
Reference in New Issue
Block a user