ivanlog 3 месяцев назад
Сommit
a93e43d0d3

+ 15 - 0
common/accessTypeEnum.md

@@ -0,0 +1,15 @@
+# AccessTypeEnum 
+
+Перечисление доступов пользователей и сервисов
+
+*    Unknown = 0,
+*    ArticlesControl = 100,
+*    UsersControl = 101,
+*    SupportControl = 102,
+*    HeadHuntControl = 103,
+*    FavoritesView = 104, //Просмотр избранных товаров
+*    LoyaltyControl = 105,
+*    LoyaltyProcessing = 106,
+*    ReviewsQuestionsControl = 107,
+*    ExternalMasterSystem = 200,
+*    SuperAdmin = 255 //Супер-админ, который имеет все доступы сразу

+ 135 - 0
docs/architecture.md

@@ -0,0 +1,135 @@
+# Архитектура проекта
+
+## Структура
+
+Проект делится на следующие решения:
+   * Сервер балансировки (nginx)
+   * Мастер‑система (1С‑сервер)
+   * Бэкенд
+      * PlatformAPI (агрегация микросервисов в узел)
+      * Набор микросервисов
+   * Фронтенд (PWA-приложения)
+      * Интернет магазин (Shop PWA)
+      * Административное приложение (Admin PWA)
+
+### Сервер балансировки
+
+В качестве сервера балансироваки используется [nginx](https://nginx.org/). По текущим нагрузкам не требуются какие-то особенные настройки. 
+
+[Посмотреть текущие настройки](/docs/backend/nginx_conf.md)
+
+### Мастер‑система (1С‑сервер)
+
+Находится на стороне заказчика. Не имеет публичных точек подключения. Взаимодействие осуществляется через микросервис Connector1CService (см. документацию в коде).
+
+### Фронтенд
+
+* Интернет магазин (Shop PWA) - web-сайт магазин строительных материалов и товаров для дома (каталог, поиск, карточка товара, корзина, оформление заказа, личный кабинет, отзывы/вопросы, избранное). 
+
+* Административное приложение (Admin PWA) - инструменты для сотрудников: управление справочниками, публикация контента и статей, модерация отзывов и вопросов, настройка акций/промо, просмотр и обработка заказов и доставок, ответы службы поддержки.
+
+PWA взаимодействую с системой через сервер балансировки и PlatformAPI. Для взаимодействия используются HTTP-запросы. PlatformAPI проверяет доступы и права и контролирует разрешенность запросов к микросервисам на основе выданных прав пользователю. 
+
+[Подробнее](/docs/frontend/index.md)
+
+#### Интернет магазин
+
+Микросервисы, с которыеми имеется взаимодействие:
+
+* AuthService - аутентификация и авторизация (телефон, SMS/flash-call, выдача токена, управление сессией).
+* ProfilesService - управление данными пользователя, получение информации о своем пользователе.
+* FilesDirectoryService - хранение, управление файлами/медиа и их получение.
+* SearchService - поиск нужного набора товаров по заданным параметрам.
+* DirectoryService - получение информации о категориях, брендах, товаров.
+* FavoriteService - функционал избранных товаров.
+* HeadHuntService - получение списка вакансий, оставление заявки.
+* BusinessService - корзина, формирование заказа, получение информации о заказах.
+* ReviewsService - получение существующих и создание новых отзывов и вопросов о товарах
+* ArticlesService - получение публикация и статей
+* SupportService - формирование запроса в техподдержку
+* DeliveryService - формирование рассчета стоимости доставки с учетом параметров.
+
+Так же интернет магазин взаимодействуюет со следующими внешними сервисами, через подключенные скрипты:
+
+* Яндекс метрика
+* Top.mail.ru counter
+* Calltouch (на уровне кода отправляет помеченные события (см. код))
+* AnyQuery (поисковая аналитика)
+
+
+#### Административное приложение
+
+Микросервисы, с которыеми имеется взаимодействие:
+
+* AuthService - аутентификация и авторизация (телефон, SMS/flash-call, выдача токена, управление сессией).
+* ProfilesService - управление пользователями, профили, доступы, телефоны, email.
+* FilesDirectoryService - хранение, управление файлами/медиа и их получение.
+* SearchService - индексация и поиск: работа с синонимами, опечатками, выдача релевантных товаров.
+* DirectoryService - получение информации о категориях, брендах, товаров.
+* HeadHuntService - вакансии: публикация, редактирование, архивирование офферов, отклики и их обработка.
+* ReviewsService - отзывы и вопросы: постинг, модерация, лайки/дизлайки, отчёты по товарам.
+* ArticlesService - публикация статей: создание, обновление, архивирование, публикация, выборка.
+* SupportService - техподдержка: закрытие запросов, выборка запросов.
+
+### Мастер-система 1С
+
+1С на стороне заказчика является **источником истины** для товарного каталога, цен, остатков и статусов заказов. Она обменивается данными с платформой через специальный сервис-адаптер — **Connector1CService**, который преобразует данные из формата 1С в формат платформы и обратно. Таким образом достигается совместимость без жёсткой привязки архитектуры к особенностям 1С.
+
+### Бэкенд
+
+Бэкэнд представлен набором микросервисов и агрегатором ProfiMall.ServerAPI (PlatformAPI), который открывает внешний доступ к ним через API поддерживая OpenAPI.
+Микросервисы взаимодействуют между собой на функциональном уровне в пределах одного приложения, если они работают в пределах одного домена (агрегатора).
+При необходимости (увеличенные нагрузки, нехватка мощностей) отдельные микросервисы можно вынести в отдельный домен на другой физический (или виртуальный) сервер. В этом случае их взаимодействие будет через HTTP API.
+
+[Подробнее](/docs/backend/index.md) //TODO уделить больше внимания архитектуре бэкэнда (программным слоям)
+
+#### Микросервисы
+
+Детальная информация по сервисам и их моделям по ссылкам.
+
+* [AuthService](/docs/backend/services/auth.md) - аутентификация и авторизация (телефон, SMS/flash-call, выдача токена, управление сессией).
+* [ProfilesService](/docs/backend/services/profiles.md) - управление пользователями, профили, доступы, телефоны, email.
+* [DirectoryService](/docs/backend/services/directory.md) - справочники: категории, бренды, товары, фильтрация и сортировка, синхронизация.
+* [FilesDirectoryService](/docs/backend/services/profiles.md) - хранение и управление файлами/медиа: загрузка, резервирование токенов, проверка существования.
+* [SearchService](/docs/backend/services/search.md) - индексация и поиск: работа с синонимами, опечатками, выдача релевантных товаров.
+* [BusinessService](/docs/backend/services/business.md) - корзина, заказы: создание, отмена, обновления, статусы, взаимодействие с мастер-системой.
+* [Connector1CService](/docs/backend/services/connector1C.md) - интеграция с 1С: каталоги, остатки, цены, заказы, клиенты, синхронизация.
+* [FavoriteService](/docs/backend/services/favorite.md) - избранное: добавление/удаление товаров, выборка избранных позиций.
+* [HeadHuntService](/docs/backend/services/headhunt.md) - вакансии: публикация, редактирование, архивирование офферов, отклики и их обработка.
+* [ReviewsService](/docs/backend/services/reviews.md) - отзывы и вопросы: постинг, модерация, лайки/дизлайки, отчёты по товарам.
+* [ArticlesService](/docs/backend/services/articles.md) - публикация статей: создание, обновление, архивирование, публикация, выборка.
+* [SupportService](/docs/backend/services/support.md) - техподдержка: создание обращений, закрытие, выборка запросов.
+* [DeliveryService](/docs/backend/services/delivery.md) - тарифы и информация по доставке, расчёт стоимости и сроков.
+* **LoyaltyService** - промо-кампании, промокоды, скидки на заказы, обработка подтверждений (не запущен).
+
+
+## Общая схема архитектуры
+
+```
+                              |-> [Profiles]
+                              |-> [Auth]
+                              |-> [FilesDirectory]
+                              |-> [Search]
+                              |-> [Directory]
+                              |-> [Favorites]
+                              |-> [HeadHunt]
+                              |-> [Loyalty]
+                              |-> [Business]
+                              |-> [Reviews]
+                              |-> [Articles]
+                              |-> [Support]
+                              |-> [Delivery]
+                              |-> [Connector1C]
+                              |
+   [PlatformAPI_0](МС_0, М_1, ...) + [PlatformAPI_N](МС_N+1, М_N+2, ...)
+   _____________________________________________________________________
+                                 ^
+                                 v
+                     [1C] <-> [nginx] <-> [Внешние сервисы]
+                                 ^
+                                / \
+                               v   v
+                     [Shop PWA] [Admin PWA]
+```
+
+[Назад](/index.md)

+ 142 - 0
docs/backend/index.md

@@ -0,0 +1,142 @@
+# Бэкэнд
+
+Бэкэнд представлен набором микросервисов и агрегатором ProfiMall.ServerAPI (PlatformAPI), который открывает внешний доступ к ним через API поддерживая OpenAPI.
+Микросервисы взаимодействуют между собой на функциональном уровне в пределах одного приложения, если они работают в пределах одного домена (агрегатора).
+При необходимости (увеличенные нагрузки, нехватка мощностей) отдельные микросервисы можно вынести в отдельный домен на другой физический (или виртуальный) сервер. В этом случае их взаимодействие будет через HTTP API.
+
+## Стек
+
+* Проект
+    * C# (.NET 8.0) - язык разработки
+    * Dapper - ORM
+    * AutoMapper - мэппинг
+    * SQLite - СУБД
+    * SixLabors.ImageSharp - работа с изображениями
+    * BStorm - набор инструментов
+    * NetBridge - библиотека обертки программных интерфейсов в HTTP API (OpenAPI v3) 
+    * Yandex.Checkout.V3 - библиотека для работы с ЮКассой
+
+## Структура решения
+
+- backend
+    - ProfiMall [общее решение, библиотека содержит микросервисы и общий код]
+        - Common [общий код]
+        - *Название_сервиса [папка с кодом сервиса]
+    - ProfiMall.ServerAPI [решение PlatformAPI, приложение объединяющее микросервисы]
+        - conf [глобальные конфигурационные файлы]
+        - Classes
+        - Middlewares 
+
+- сборка приложения
+    - services
+        - *название сервиса 
+            - *название сервиса.db [БД сервиса]
+            - * [различные конфигруационные файлы сервиса]
+    - conf [глобальные конфигурационные файлы]
+        - loggerConfig.json [конфиг логгера]
+        - NBAuthConfig.json [конфиг NetBridge аутентификации]
+    - filestorage [хранилище загруженны файлов сервиса FilesDirectoryService]
+        - *категория [категория файлов]
+            - * [загруженные файлы]
+    - logs 
+        - *название сервиса
+            - * [логи сервиса]
+    - *язык (локализации библиотек)
+
+## Код
+
+Приложение построено на NetBridge, который позволяет из программного интерфейса получить набор HTTP API эндпойнтов. 
+
+NB так же контролирует доступ к ресурсам на основе ассинхронного контекста через NBContext -> NBSession (см.код). 
+
+### ProfiMall.ServerAPI
+
+Выполняемое приложение. Собирает необходимые сервисы в один домен и предоставляет к ним доступ через OpenAPI.
+
+* Program.cs - точка входа в приложение.
+* ProfiMallCore.cs - основное ядро (см.код)
+
+Приложение запускает на http://127.0.0.1:5000 HTTP сервер.
+Доступ к OpenAPI v3 интерфейсу через http://127.0.0.1:5000/swagger/index.html
+
+### ProfiMall
+
+Библиотека.
+
+* Common - общий набор интерфейсов, классов, исключений, енумов (см. код).
+* *Микросервисы - отдельные сервисы по папкам.
+
+Большинство микросервисов построены по одинаковой структуре.
+- conf [Папка с конфигами, если они есть]
+- Enums [енумы]
+- Models [модели]
+- Internal [внутренние классы, как модели, объекты доступа к базе, миграции]
+    - *DAL [описание БД]
+    - *DBMigration [миграции]
+
+БД (SQLite) используется для долгосрочного хранения данных, к которым не требуется постоянный доступ. 
+Если к данные активно используются, то они кэшируются в словарях. 
+
+#### Микросервисы
+
+Детальная информация по сервисам и их моделям по ссылкам.
+
+* [AuthService](/docs/backend/services/auth.md) - аутентификация и авторизация (телефон, SMS/flash-call, выдача токена, управление сессией).
+* [ProfilesService](/docs/backend/services/profiles.md) - управление пользователями, профили, доступы, телефоны, email.
+* [DirectoryService](/docs/backend/services/directory.md) - справочники: категории, бренды, товары, фильтрация и сортировка, синхронизация.
+* [FilesDirectoryService](/docs/backend/services/profiles.md) - хранение и управление файлами/медиа: загрузка, резервирование токенов, проверка существования.
+* [SearchService](/docs/backend/services/search.md) - индексация и поиск: работа с синонимами, опечатками, выдача релевантных товаров.
+* [BusinessService](/docs/backend/services/business.md) - корзина, заказы: создание, отмена, обновления, статусы, взаимодействие с мастер-системой.
+* [Connector1CService](/docs/backend/services/connector1C.md) - интеграция с 1С: каталоги, остатки, цены, заказы, клиенты, синхронизация.
+* [FavoriteService](/docs/backend/services/favorite.md) - избранное: добавление/удаление товаров, выборка избранных позиций.
+* [HeadHuntService](/docs/backend/services/headhunt.md) - вакансии: публикация, редактирование, архивирование офферов, отклики и их обработка.
+* [ReviewsService](/docs/backend/services/reviews.md) - отзывы и вопросы: постинг, модерация, лайки/дизлайки, отчёты по товарам.
+* [ArticlesService](/docs/backend/services/articles.md) - публикация статей: создание, обновление, архивирование, публикация, выборка.
+* [SupportService](/docs/backend/services/support.md) - техподдержка: создание обращений, закрытие, выборка запросов.
+* [DeliveryService](/docs/backend/services/delivery.md) - тарифы и информация по доставке, расчёт стоимости и сроков.
+* **LoyaltyService** - промо-кампании, промокоды, скидки на заказы, обработка подтверждений (не запущен).
+
+## Нотации
+
+### Код
+
+* Каждый сервис должен именть интерфейс для возможности работы с NB 
+* Стремимся делать иммутабельные объекты и коллекции. (см. код)
+
+### Документирование
+
+* Использование комментариев для документирования. Обязательно на уровне интерфейсов.
+* Использование простых комментариев в сложных блоках кода.
+* Использование понятного наименования, которые позволит корректно читать код и его функционал.
+* Общее описание функционала писать во внешнуюю документацию.
+
+### Версионность
+
+Используем Git на https://git.ivanlog.ru/
+
+Репозиторий: https://git.ivanlog.ru/ProfiMall/Backend
+
+Запрос доступа у ivanlog.
+
+Основная ветка разработки - dev.
+Каждый разработчик заводит свою ветку по виду dev-имя_разработчика. В ней ведет разработку отдельный фич, после мержит в dev.
+Основная ветка деплоя - main.
+
+## Технические требования
+
+* Для разработки - особых требований нет. 
+
+* Для деплоя:
+    * ОС: Windows/Ubuntu 22.04 (зависи от настроек сборки)
+    * Если сборка не нативная, то необходимо установить:
+        * dotnet-runtime-8.0
+        * aspnetcore-runtime-8.0
+    * Системные требования:
+        * от 2 ядер ЦП
+        * от 4 ГБ ОЗУ
+        * от 40 ГБ SSD
+        * от 100 Мб/c канал связи
+        * увеличивать по мере роста нагрузки
+
+
+[Назад](/index.md)

+ 214 - 0
docs/backend/nginx_conf.md

@@ -0,0 +1,214 @@
+# Текущая конфигурации nginx
+
+
+## nginx.conf
+
+```
+user www-data;
+worker_processes auto;
+pid /run/nginx.pid;
+include /etc/nginx/modules-enabled/*.conf;
+
+events {
+	worker_connections 768;
+	# multi_accept on;
+}
+
+http {
+
+	##
+	# Basic Settings
+	##
+
+	sendfile on;
+	tcp_nopush on;
+	types_hash_max_size 2048;
+	# server_tokens off;
+
+	client_max_body_size 100M;
+
+	# server_names_hash_bucket_size 64;
+	# server_name_in_redirect off;
+
+	include /etc/nginx/mime.types;
+	default_type application/octet-stream;
+
+	##
+	# SSL Settings
+	##
+
+	ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
+	ssl_prefer_server_ciphers on;
+
+	##
+	# Logging Settings
+	##
+
+	access_log /var/log/nginx/access.log;
+	error_log /var/log/nginx/error.log;
+
+	##
+	# Gzip Settings
+	##
+
+	gzip on;
+	gzip_disable "msie6";
+	gzip_vary on;
+	gzip_proxied any;
+	gzip_comp_level 6;
+	gzip_min_length 0; 
+	gzip_types text/css application/javascript application/json;
+	# gzip_vary on;
+	# gzip_proxied any;
+	# gzip_comp_level 6;
+	# gzip_buffers 16 8k;
+	# gzip_http_version 1.1;
+	# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
+
+	##
+	# Virtual Host Configs
+	##
+
+	include /etc/nginx/conf.d/*.conf;
+	include /etc/nginx/sites-enabled/*;
+}
+```
+
+
+## profimoll.ru
+
+```
+server {
+    server_name profimoll.ru;
+
+    root /home/www/profimoll.ru/htdocs;
+    index index.html;
+
+	#Выключаем кэш для основного html
+	location = /index.html {
+		add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0";
+		add_header Pragma "no-cache";
+		add_header Expires 0;
+	}	
+
+
+	location /fs/ {
+		alias /home/services/profimall_api/filesstorage/;
+	}
+
+	location /css {
+		root /home/www/profimoll.ru/htdocs;
+	}
+	location /files {
+		root /home/www/profimoll.ru/htdocs;
+	}
+	location /js {
+		root /home/www/profimoll.ru/htdocs;
+	}
+
+    location /api/ {
+        proxy_pass         http://127.0.0.1:5000/;
+        proxy_http_version 1.1;
+        proxy_set_header   Host $host;
+        proxy_set_header   X-Real-IP $remote_addr;
+        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header   X-Forwarded-Proto $scheme;
+    }
+
+	location / {
+			try_files $uri /index.html;
+		}
+
+    listen 443 ssl; # managed by Certbot
+#    ssl_certificate /etc/letsencrypt/live/profimoll.ru/fullchain.pem; # managed by Certbot
+#    ssl_certificate_key /etc/letsencrypt/live/profimoll.ru/privkey.pem; # managed by Certbot
+    ssl_certificate /etc/ssl/private/profimoll.ru.crt; 
+    ssl_certificate_key /etc/ssl/private/profimoll.ru.key; 
+
+    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
+    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
+
+}
+server {
+    if ($host = profimoll.ru) {
+        return 301 https://$host$request_uri;
+    } # managed by Certbot
+
+
+    listen 80;
+    server_name profimoll.ru;
+    return 404; # managed by Certbot
+
+
+}
+
+```
+
+
+## admin.profimoll.ru
+
+```
+server {
+    server_name admin.profimoll.ru;
+
+    root /home/www/admin.profimoll.ru/htdocs;
+    index index.html;
+
+	#Выключаем кэш для основного html
+	location = /index.html {
+		add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0";
+		add_header Pragma "no-cache";
+		add_header Expires 0;
+	}	
+
+
+	location /fs/ {
+		alias /home/services/profimall_api/filesstorage/;
+	}
+
+	location /css {
+		root /home/www/admin.profimoll.ru/htdocs;
+	}
+	location /files {
+		root /home/www/admin.profimoll.ru/htdocs;
+	}
+	location /js {
+		root /home/www/admin.profimoll.ru/htdocs;
+	}
+
+    location /api/ {
+        proxy_pass         http://127.0.0.1:5000/;
+        proxy_http_version 1.1;
+        proxy_set_header   Host $host;
+        proxy_set_header   X-Real-IP $remote_addr;
+        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header   X-Forwarded-Proto $scheme;
+    }
+
+	location / {
+			try_files $uri /index.html;
+		}
+
+    listen 443 ssl; # managed by Certbot
+    ssl_certificate /etc/letsencrypt/live/admin.profimoll.ru/fullchain.pem; # managed by Certbot
+    ssl_certificate_key /etc/letsencrypt/live/admin.profimoll.ru/privkey.pem; # managed by Certbot
+    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
+    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
+
+}
+server {
+    if ($host = admin.profimoll.ru) {
+        return 301 https://$host$request_uri;
+    } # managed by Certbot
+
+
+    listen 80;
+    server_name admin.profimoll.ru;
+    return 404; # managed by Certbot
+
+
+}
+```
+
+
+[Назад](/index.md)

+ 102 - 0
docs/backend/services/articles.md

@@ -0,0 +1,102 @@
+# ArticlesService
+
+## Назначение
+
+Сервис управляет статьями контент-раздела (инфо-материалы, новости, акции). Все статьи имеют уникальные ID из одной последовательности, но различаются по типу (`ArticleTypeEnum`). Создание и изменение выполняется единым методом `UpdateAsync`: если `Id == 0`, создаётся новая статья заданного типа; иначе обновляются изменяемые поля существующей. Запрет на несоответствие типа и `Id` (например, передача статьи одного типа с `Id` от другого) приводит к ошибке валидности данных. Контроль доступа - по данным сессии (NBSession).
+
+## Публичные методы
+
+* `ArchiveAsync(articleId): void` - пометка статьи как архивной.
+* `SetPublishedAsync(articleId, isPublished): void` - переключение статуса публикации.
+* `UpdateAsync(article): Article` - создание или обновление статьи.
+* `GetArticlesAsync(type, skip = 0, take = 12, onlyIsPublished = true): ArticlesList` - выборка статей по типу с пагинацией; `onlyIsPublished = false` допустимо только для админов.
+* `GetArticleAsync(id): Article` - получить статью по идентификатору.
+
+## Модели
+
+### Article
+
+```
+int Id – идентификатор статьи
+ArticleTypeEnum Type – тип статьи (Unknown = 0, Info = 1, News = 2, Sale = 3)
+string Alias – псевдоним для SEO/ссылок
+string MetaKeywords – SEO ключевые слова
+string MetaDescription – SEO описание
+string Title – заголовок статьи
+string Tag – тег или рубрика
+string DescriptionShort – краткое описание
+string ContentHTML – полный контент в формате HTML
+FileDescriptor Foto – основное изображение
+FileDescriptor FotoTile – изображение для тайла/превью
+int ExpectReadTimeMinutes – ожидаемое время чтения в минутах
+long ShowedCount – счётчик просмотров
+int DiscountCategoryId – id категории, связанной с акцией
+int[] DiscountProductIds – список id товаров для промо
+DateTimeOffset StartTime – дата начала публикации
+DateTimeOffset? EndTime – дата окончания публикации (опционально)
+DateTimeOffset Created – дата создания статьи
+bool IsPublished – признак опубликованности
+bool IsArchived – признак архивирования
+bool IsActive – вычисляемое поле, актуальность статьи (не хранится в БД)
+```
+### ArticlesList
+
+```
+int TotalCount – общее количество статей по запросу
+Article[] List – список статей на текущей странице
+```
+
+### ArticleTypeEnum
+
+```
+Unknown = 0 – неопределённый тип
+Info = 1 – информационный материал
+News = 2 – новость
+Sale = 3 – акция/промо
+```
+
+## Правила и инварианты
+
+* **Единая последовательность ID**: все типы статей разделяют один счётчик идентификаторов, тип задаётся отдельно. Любая попытка «сменить тип под старым Id» должна блокироваться валидаторами.
+* **Публикация vs активность**: `IsPublished` и `IsArchived` управляются отдельными методами сервиса; `IsActive` - вычисляемое поле по времени окна публикации (`StartTime`/`EndTime`) и статусам, не хранится в БД.
+* **Контроль доступа**: операции чтения/управления учитывают сессионные права; параметр `onlyIsPublished=false` игнорируется для не-админов.
+* **Медиа через файловый сервис**: `Foto`/`FotoTile` - `FileDescriptor` из FilesDirectoryService; при обновлении статьи сервис должен валидировать наличие/категорию файлов согласно правилам файлового сервиса.
+* **Потокобезопасность**: операции `CheckActive` и `Update` используют внутренний `lock`, что исключает гонки при одновременных обновлениях и вычислении активности.
+
+## DB
+
+Содержит таблицы и индексы
+
+```
+-- Migration 1
+articles (
+    Id INTEGER PRIMARY KEY AUTOINCREMENT,
+    Type INTEGER NOT NULL DEFAULT 1,
+
+    Alias TEXT NOT NULL,
+    MetaKeywords TEXT NOT NULL,
+    MetaDescription TEXT NOT NULL,
+    Title TEXT NOT NULL,
+    Tag TEXT NOT NULL,
+    DescriptionShort TEXT NOT NULL,
+    ContentHTML TEXT NOT NULL,
+
+    Foto TEXT NOT NULL DEFAULT '{}',
+    FotoTile TEXT NOT NULL DEFAULT '{}',
+    ExpectReadTimeMinutes INTEGER NOT NULL DEFAULT 0,
+    ShowedCount INTEGER NOT NULL DEFAULT 0,
+
+    DiscountCategoryId INTEGER NOT NULL DEFAULT 0,
+    DiscountProductIds TEXT NOT NULL DEFAULT '[]',
+
+    StartTime NUMBER NOT NULL,
+    EndTime NUMBER NOT NULL,
+    Created NUMBER NOT NULL,
+
+    IsPublished BOOLEAN NOT NULL DEFAULT FALSE,
+    IsArchived BOOLEAN NOT NULL DEFAULT FALSE,
+    IsActive BOOLEAN NOT NULL DEFAULT FALSE
+)
+```
+
+[Назад](/index.md)

+ 88 - 0
docs/backend/services/auth.md

@@ -0,0 +1,88 @@
+# AuthService
+
+## Назначение
+
+AuthService обеспечивает аутентификацию и авторизацию пользователей. Основной сценарий входа - по номеру телефона с подтверждением через SMS или flash-call. Сервис управляет жизненным циклом сессий и выпускает сессионные токены для доступа к остальным микросервисам.
+
+## Публичные методы AuthService
+
+* `TryAuthByPhoneAsync(phone): string` - инициация входа: генерация `sid`, отправка кода подтверждения.
+* `ConfirmAuthCodeAsync(code): string` - проверка кода из SMS и подтверждение телефона и `sid`.
+* `CheckAuthOrEmptyAsync(withRefresh): string` - выпуск `authtoken` либо пустой строки.
+* `TryChangePhoneAsync(phone): User` - смена привязанного к пользователю номера телефона.
+* `DropAuthSessionAsync(): void` - завершение сессии.
+
+## Взаимодействие с ProfilesService
+
+```
+[AuthService] -> [ProfilesService]
+```
+
+AuthService сам не хранит данных о пользователях и делегирует работу с профилями в ProfilesService.
+После подтверждения номера телефона AuthService взаимодействует с ProfilesService:
+
+1. **Изменение телефона пользователя**:
+
+```csharp
+var user = await profiles.ChangeUserPhoneAsync(session.UserId, tryCodes.Auth.Data);
+```
+
+2. **Проверка и создание пользователя по номеру**:
+
+```csharp
+var user = await profiles.GetUserOrNullByPhoneAsync(tryCodes.Auth.Data);
+user ??= await profiles.CreateUserByPhoneAsync(tryCodes.Auth.Data);
+```
+
+AuthService отвечает за подтверждение личности, а ProfilesService - за хранение и управление данными профиля. Их связка гарантирует, что любая успешная авторизация всегда соответствует реальной записи пользователя в системе.
+
+## Модели
+### AuthSession
+
+AuthService хранит все пользовательские сессии в моделе AuthSession. Активные сессии кэшируются в словарях сервиса, остальные хранятся в SQLite.
+
+```
+string Sid – идентификатор сессии (session id)
+long UserId – идентификатор пользователя, к которому относится сессия
+DateTimeOffset Created – дата и время создания сессии
+DateTimeOffset LastActive – время последней активности
+AuthToken? AuthToken – выданный токен аутентификации для этой сессии
+bool IsDeleted – признак, что сессия удалена
+```
+
+### AuthToken
+
+Модель описывающая доступы (права) пользователя. Имеет цифровую подпись сервиса выдавшего его, сроки годности. Через CheckAuthOrEmptyAsync можно запросить новый токен. Сервис по SID поймет какой пользователь делает запрос и выдаст новый токен. 
+
+```
+long UserId – идентификатор пользователя, для которого выпущен токен
+Access[] Accesses – список доступов (прав и ролей) пользователя
+long CreatedUnixMS – время создания токена в Unix-миллисекундах
+long UntilUnixMS – время окончания действия токена в Unix-миллисекундах
+string Sign – криптографическая подпись токена
+```
+
+### Access
+
+```
+AccessTypeEnum Type – тип доступа (роль/право)
+string Data – дополнительная строка данных (контекст доступа)
+DateTimeOffset UntilTime – время окончания действия доступа
+```
+## DB
+
+Содержит таблицы и индексы
+
+```
+-- Migration 1
+sessions (
+    Sid TEXT UNIQUE PRIMARY KEY, --Сессионный ключ, который передается в Headers
+    UserId INTEGER DEFAULT 0,
+    Created NUMERIC DEFAULT 0,
+    LastActive NUMERIC DEFAULT 0,
+    AuthToken TEXT NOT NULL, --Текущий токен доступа
+    IsDeleted BOOLEAN DEFAULT FALSE
+)
+```
+
+[Назад](/index.md)

+ 243 - 0
docs/backend/services/business.md

@@ -0,0 +1,243 @@
+# BusinessService
+## Назначение
+
+Управление остатками и заказами: получение/обновление остатков, создание заказа по счёту (invoice), ожидание подтверждений мастер-системы и статусов оплаты, отмена, выборки заказов пользователя, а также приём внешних обновлений по заказам.
+
+## Публичные методы
+
+### Остатки
+
+* `GetRemaindersAsync(productsIds[]): decimal[]` - получить остатки по товарам.
+* `FilterInStockAsync(productsIds[]): int[]` - оставить только товары «в наличии».
+* `UpdateAllRemaindersAsync(pack): void` - полное обновление остатков.
+* `UpdateRemaindersAsync(pack): void` - частичное обновление остатков.
+
+### Создание/управление заказом (жизненный цикл)
+
+* `CreateOrderAsync(invoice): Order` - создать заказ по счёту (без подтверждения мастер-системы).
+* `AwaitOrderConfirmMasterSystemLPAsync(orderId): Order` - дождаться подтверждения мастер-системы.
+* `AwaitOrderPaymentPendingLPAsync(orderId): Order` - дождаться перехода платежа в ожидание/обработку.
+* `CancelOrderByCustomerAsync(orderId): Order` - отменить заказ пользователем.
+
+### Заказы пользователя
+
+* `GetMyOrders(skip = 0, take = 20): Order[]` - получить мои заказы с пагинацией.
+
+### Взаимодействие с внешней системой заказов (обновления)
+
+* `UpdateOrderProductsAsync(order): OrdersPairExt` - обновить состав товаров заказа.
+* `UpdateOrdersStatusAsync(orders[]): void` - массовое обновление статусов.
+* `UpdateOrderStatusToIssuedToCustomerAsync(orderStatus): OrderExt` - проставить статус «выдан покупателю».
+* `GetOrdersUpdatedLPAsync(): OrderExt[]` - получить заказы, обновлённые (long-polling вариант).
+* `GetOrdersUpdatedAsync(): OrderExt[]` - получить заказы, обновлённые (обычный вариант).
+* `GetLastOrderAsync(): OrderExt` - получить последний заказ.
+* `QueryOrdersExternalAsync(ordersIds[]): OrderExt[]` - запросить заказы по внешним идентификаторам.
+
+
+## Конфигурация BusinessService
+
+Работа сервиса опирается на конфигурационный файл `BusinessServiceConfig`. В нём содержатся параметры для интеграции с платёжным шлюзом и настройки доставки. Конфиг хранится в секции `config.json` и загружается при инициализации сервиса.
+
+### Параметры платежей (YooKassa)
+
+* **`PaymentYooKassa_ShopId`** - идентификатор магазина в YooKassa.
+* **`PaymentYooKassa_SecretKey`** - секретный ключ для подписи и проверки запросов.
+* **`PaymentYooKassa_ReturnURL`** - URL, на который возвращается пользователь после завершения платежа.
+
+> Эти параметры обязательны для корректной работы онлайн-оплаты.
+
+### Параметры доставки
+
+* **`DeliveryMasterSystemId`** - внешний идентификатор доставки в мастер-системе (1С).
+* **`DeliveryName`** - отображаемое название услуги доставки (по умолчанию «Доставка»).
+* **`DeliveryVAT`** - ставка НДС, применяемая к услуге доставки (значение из `VATCodeEnum`, по умолчанию `Vat20`).
+
+### Особенности
+
+* Конфигурация выносится в отдельный JSON и может отличаться между окружениями (Dev/Staging/Prod).
+* В боевом окружении рекомендуется хранить ключи (`SecretKey`) в защищённом хранилище или использовать секреты CI/CD.
+* Параметры доставки синхронизируются с мастер-системой и должны совпадать с её справочниками.
+
+## Взаимодействие c другими сервисами
+
+BusinessService взаимодействует со следующими сервисами:
+
+* DirectoryService - (get only) полученине данных о товарах.
+* DeliveryService - (get only) проверочный рассчет доставки по заказу.
+* ProfilesService - (get only) получение данных пользователя совершающего заказ (User).
+
+## Модели
+
+### BusinessServiceConfig
+
+```
+string PaymentYooKassa_ShopId //Идентификатор магазина YooKassa
+string PaymentYooKassa_SecretKey //Секретный ключ YooKassa
+string PaymentYooKassa_ReturnURL //URL возврата после оплаты
+
+string DeliveryMasterSystemId //Внешний идентификатор доставки в мастер-системе
+string DeliveryName //Отображаемое название доставки
+VATCodeEnum DeliveryVAT //Код НДС, применяемый к доставке
+```
+
+Конфигурационная модель сервиса; содержит настройки платёжного шлюза (YooKassa) и параметры доставки.
+
+
+### Order
+
+```
+long Id //Идентификатор заказа
+long ForwardFromId //Ид источника/переноса (если есть)
+string ExtId //Внешний идентификатор заказа (мастер-система)
+OrderStatusEnum Status //Текущий статус заказа
+string[] Discounts //Применённые скидки/промокоды
+OrderProduct[] Products //Состав заказа
+decimal TotalCost //Итоговая стоимость
+DateTimeOffset Created //Когда создан
+DateTimeOffset Updated //Когда обновлён (последняя синхронизация)
+OrderPayment PaymentInfo //Информация об оплате
+OrderDelivery Delivery //Информация о доставке
+long CustomerId //Идентификатор покупателя (если авторизован)
+OrderCustomer Customer //Данные покупателя
+string Comment //Комментарий покупателя/оператора
+bool IsPayed //Факт оплаты
+OrderPaymentStatusEnum PaymentStatus //Статус платежа
+OrderStatusEnum StatusInternal //Внутренний статус (служебный)
+bool IsClosed //Заказ закрыт
+bool IsNeedUpdateForMasterSystem //Требуется обновление в мастер-системе
+```
+
+### OrderProduct
+
+```
+int Id //Идентификатор товара
+string ExtId //Внешний ИД товара
+string Name //Наименование
+decimal Count //Кол-во, принятое к исполнению (по нему считается стоимость)
+decimal CountInit //Кол-во, выбранное клиентом
+decimal CostPerOne //Цена за единицу
+decimal CostWithDiscountPerOne //Цена со скидкой за единицу
+decimal TotalPrice //Итоговая стоимость позиции
+VATCodeEnum VAT //Код НДС
+TradeAgentSignEnum TradeAgentSign //Признак торгового агента
+TradeAgent TradeAgent //Данные торгового агента
+```
+
+### OrderCustomer
+
+```
+long Id //Ид пользователя (0 - если не авторизован)
+string Phone //Телефон
+string Email //Email
+string Name //Имя
+string PhotoFile //Идентификатор файла аватара
+DateTimeOffset? BirthDay //Дата рождения (опционально)
+```
+
+Срез пользовательских данных, фиксируемый в заказе.
+
+### OrderDelivery
+
+```
+OrderDeliveryTypeEnum Type //Тип доставки (самовывоз/курьер и т. п.)
+decimal Price //Стоимость доставки
+Coordinates Pin //Координаты точки доставки/самовывоза
+string Address //Адрес доставки/пункта
+DeliveryInfo DeliveryInfo //Детали расчёта (зона/тариф/доступность)
+```
+
+Параметры выбранной доставки; есть статическая заготовка SelfPickUp.
+
+### OrderPayment
+
+```
+OrderPaymentTypeEnum Type //Тип оплаты (онлайн, офлайн и т. п.)
+string PaymentGate //Платёжный провайдер/шлюз
+OrderPaymentDetails Payment //Детали платежа
+OrderPaymentDetails Refund //Детали возврата (если был)
+```
+
+### OrderPaymentDetails
+
+```
+decimal Amount //Сумма платежа/возврата
+string PaymentInternalId //Внутренний идентификатор
+string PaymentId //Идентификатор у платёжного провайдера
+string PaymentURL //Ссылка на оплату (если применяется)
+string Status //Статус у провайдера (текстом)
+```
+
+### OrderInvoiceItem
+
+```
+int ProductId //Идентификатор товара
+decimal Amount //Количество
+```
+
+### OrderInvoice
+
+```
+OrderInvoiceItem[] Items //Позиции счёта
+string[] Discounts //Скидки/промокоды
+decimal CheckCost //Контрольная сумма (ожидаемый итог от клиента)
+OrderDelivery Delivery //Параметры доставки
+OrderPaymentTypeEnum PaymentType //Способ оплаты
+string UserEmail //Email покупателя (если не авторизован)
+string UserName //Имя покупателя (если не авторизован)
+string Comment //Комментарий покупателя
+```
+
+## DB
+
+
+Содержит таблицы и индексы
+
+```
+-- Migration 1
+remainders (
+    ProductId INTEGER PRIMARY KEY,
+    Value TEXT NOT NULL DEFAULT '0'
+)
+```
+
+```
+-- Migration 2
+orders (
+    Id INTEGER PRIMARY KEY AUTOINCREMENT,
+    ExtId TEXT NOT NULL DEFAULT '',
+    Status INTEGER NOT NULL,
+    Discounts TEXT NOT NULL DEFAULT '[]',
+    Products TEXT NOT NULL DEFAULT '[]',
+    TotalCost TEXT NOT NULL DEFAULT '0',
+    Created INTEGER NOT NULL,
+    Updated INTEGER NOT NULL,
+    PaymentInfo TEXT NOT NULL DEFAULT '{}',
+    Delivery TEXT NOT NULL DEFAULT '{}',
+    CustomerId INTEGER NOT NULL,
+    Customer TEXT NOT NULL DEFAULT '{}',
+    Comment TEXT NOT NULL DEFAULT '',
+    InternalComment TEXT NOT NULL DEFAULT '',
+    IsPayed BOOLEAN NOT NULL DEFAULT FALSE,
+    IsClosed BOOLEAN NOT NULL DEFAULT FALSE,
+    IsNeedUpdateForMasterSystem BOOLEAN NOT NULL DEFAULT FALSE
+)
+
+CREATE INDEX IF NOT EXISTS idx_orders_customer_id ON orders (CustomerId);
+```
+
+```
+-- Migration 3
+ALTER TABLE orders ADD PaymentStatus INTEGER NOT NULL DEFAULT 0;
+```
+
+```
+-- Migration 4
+ALTER TABLE orders ADD StatusInternal INTEGER NOT NULL DEFAULT 0;
+```
+
+```
+-- Migration 5
+ALTER TABLE orders ADD ForwardFromId INTEGER NOT NULL DEFAULT 0;
+```
+
+[Назад](/index.md)

+ 313 - 0
docs/backend/services/connector1C.md

@@ -0,0 +1,313 @@
+# Connector1CService 
+
+## Назначение
+
+Этот сервис выступает адаптером между мастер-системой 1С и платформой. Его ключевая задача - принимать данные и команды в том виде, в котором их предоставляет 1С, трансформировать их в формат, удобный для сервисов платформы, и наоборот - подготавливать данные из платформы в том виде, который ожидает 1С.
+
+Таким образом Connector1CService:
+
+* снимает различия в форматах и структурах данных (1С ↔ микросервисы),
+* обеспечивает «единое окно» интеграции для каталогов, остатков, заказов и клиентов,
+* играет роль *буфера совместимости*, позволяя независимо развивать как мастер-систему, так и платформу.
+
+## Публичные методы 
+### Каталоги и справочники
+
+* `UpdateDirectoryAsync(pack): ResultPack1C` - приём пакета обновлений каталога от 1С.
+* `GetAllProductsAsync(): ProductExt[]` - выгрузка всех товаров в расширенном виде.
+* `GetAllCategoriesAsync(): CategoryExt[]` - выгрузка всех категорий.
+* `GetFileExtIdsToNeedUploadAsync(take): string[]` - список файлов, которые нужно дозагрузить.
+
+### Остатки
+
+* `UpdateAllRemaindersAsync(pack): ResultPack1C` - полная синхронизация остатков.
+* `UpdateRemaindersAsync(pack): ResultPack1C` - частичное обновление остатков.
+
+### Заказы
+
+* `UpdateOrderProductsAsync(order): OrdersPairExt` - обновление состава товаров в заказе.
+* `UpdateOrdersStatusAsync(orders[]): void` - пакетное обновление статусов заказов.
+* `UpdateOrderStatusToIssuedToCustomerAsync(orderStatus): OrderExt` - проставить статус «выдан покупателю».
+* `GetOrdersUpdatedLPAsync(): OrderExt[]` - выборка обновлённых заказов (long-polling).
+* `GetOrdersUpdatedAsync(): OrderExt[]` - выборка обновлённых заказов (обычный вызов).
+* `GetLastOrderAsync(): OrderExt` - получить последний заказ.
+* `QueryOrdersExternalAsync(orderIds[]): OrderExt[]` - запросить заказы по их внешним идентификаторам.
+
+### Клиенты
+
+* `GetCustomers(userIds[]): OrderCustomer[]` - получить сведения о клиентах по идентификаторам.
+
+## Особенности
+
+* **Форматирование данных:**
+  Сервис не меняет бизнес-логику, он лишь «переводит» данные между внутренними моделями (DirectoryService, BusinessService и др.) и структурами 1С.
+
+* **Двунаправленный обмен:**
+  Поддерживает как входящие запросы от 1С (обновления справочников, остатков, заказов), так и исходящие выборки (товары, категории, клиенты).
+
+* **Буфер ошибок:**
+  Методы возвращают `ResultPack1C` либо расширенные модели (`OrderExt`, `ProductExt` и т. п.), позволяя фиксировать результаты обмена и возможные ошибки синхронизации.
+
+* **Гибкость:**
+  Благодаря такому адаптеру можно модифицировать логику работы платформы, не затрагивая 1С, и наоборот - менять 1С без переписывания микросервисов.
+
+## Взаимодействие c другими сервисами
+
+Connector1CService взаимодействует со следующими сервисами:
+
+* DirectoryService - (get set) обновленные данных и получение текущих данных.
+* BusinessService - (get set) обновленные данных и получение текущих данных.
+* FilesDirectoryService - (get set) получение списка файлов на загрузку, загрузка файлов.
+
+## Модели Directory
+### ProductExt
+
+```
+string Id //ИД товара во внешней системе (1С)
+string ExtCode //Дополнительный внешний код
+string Category //ИД категории
+string Article //Артикул
+string Name //Наименование
+string NameSEO //SEO-наименование (переписывает Name)
+string Description //Описание
+string DescriptionSEO //SEO-описание (переписывает Description)
+string[] BarCodes //Штрих-коды
+int UnitCode //Код единицы измерения (ОКЕИ)
+string BrandId //ИД бренда
+FileRecordExt[] Fotos //Фото-файлы
+FileRecordExt[] Files //Другие файлы
+decimal Price //Базовая цена
+float WeightKg //Вес, кг
+Volume SizeM //Размеры (ширина/длина/высота, метры)
+float VolumeM //Объём (альтернативное поле)
+VATCodeEnum VATCode //Код НДС
+TradeAgentSignEnum TradeAgentSign //Признак торгового агента
+TradeAgent TradeAgent //Данные торгового агента
+bool IsPublished //Признак публикации
+bool IsDeleted //Признак удаления
+```
+
+Комментарий: полная витринная модель товара для обмена с 1С; маппится в `Product` платформы и обратно.
+
+### CategoryExt
+
+```
+string Id //ИД категории во внешней системе
+string ParentId //ИД родительской категории
+int Order //Порядок сортировки на уровне вложенности
+string Name //Название категории
+bool IsDeleted //Признак удаления
+```
+
+Комментарий: соответствует структуре категорий в 1С; хранит иерархию через ParentId.
+
+### BrandExt
+
+```
+string Id //ИД бренда во внешней системе
+string Name //Наименование бренда
+FileRecordExt Logo //Логотип (файл)
+bool IsDeleted //Признак удаления
+```
+
+Комментарий: модель бренда; позволяет передавать и обновлять логотипы.
+
+### ProductAttributeExt
+
+```
+string ProductId //ИД товара
+string AttributeTypeId //ИД типа атрибута
+string Value //Значение атрибута в строковом виде
+```
+
+Комментарий: связь «товар ↔ атрибут» с фактическим значением.
+
+### ProductAttributeTypeExt
+
+```
+string Id //ИД типа атрибута
+string CategoryId //ИД категории, к которой относится
+ProductAttributeTypeEnum TypeEnum //Тип атрибута
+string Name //Наименование атрибута
+int Order //Порядок отображения
+bool IsDeleted //Признак удаления
+```
+
+Комментарий: метаданные атрибута (название, тип, порядок сортировки).
+
+### FileRecordExt
+
+```
+string Id //ИД файла (во внешней системе)
+string Tag //Тип/назначение файла (например, "изображение", "инструкция")
+```
+
+Комментарий: облегчённый дескриптор файла для обмена (только ID и тег).
+
+### UpdateDataPackExt
+
+```
+long PackId //Идентификатор пакета синхронизации
+BrandExt[] Brands //Справочник брендов
+CategoryExt[] Categories //Справочник категорий
+ProductAttributeTypeExt[] ProductAttributeTypes //Типы атрибутов
+ProductExt[] Products //Товары
+ProductAttributeExt[] ProductAttributes //Значения атрибутов
+```
+
+Комментарий: пакет синхронизации от 1С; передаётся в `UpdateDirectoryAsync`, чтобы массово обновить каталог.
+
+## Модели Business
+
+### OrderExt
+
+```
+long SiteId //Ид заказа на сайте
+long ForwardFromSiteId //Ид исходного заказа на сайте (если пересоздавался)
+string Id //Внешний ид заказа (мастер-система)
+OrderStatusEnum Status //Статус заказа
+string[] Discounts //Идентификаторы применённых скидок/промокодов
+OrderProductExt[] Products //Состав заказа
+decimal TotalCost //Итоговая стоимость заказа
+long CreatedUnixTimestampMS //Момент создания (Unix ms)
+long UpdatedUnixTimestampMS //Момент обновления (Unix ms)
+OrderDeliveryExt Delivery //Информация о доставке
+long CustomerId //Id покупателя
+OrderCustomerExt Customer //Снимок данных покупателя на момент заказа
+string Comment //Комментарий клиента
+OrderPaymentExt PaymentInfo //Платёжные реквизиты (меняется только со стороны ИМ)
+OrderPaymentStatusEnum PaymentStatus //Статус оплаты (меняется только со стороны ИМ)
+OrderStatusEnum StatusInternal //Внутренний статус (меняется только со стороны ИМ)
+bool IsPayed //Флаг «оплачено» (меняется только со стороны ИМ)
+bool IsClosed //Флаг «закрыт» (меняется только со стороны ИМ)
+```
+
+Комментарий: основная «передаваемая» форма заказа между 1С и платформой; содержит как витринные, так и служебные поля.
+
+### OrderProductExt
+
+```
+string Id //Внешний ид товара (из каталога 1С)
+string Name //Наименование
+decimal Count //Количество, принятое к исполнению (по нему считается стоимость)
+decimal CountInit //Количество, выбранное клиентом
+decimal CostPerOne //Цена за единицу
+decimal CostWithDiscountPerOne //Цена за единицу с учётом скидки
+decimal TotalPrice //Итоговая стоимость позиции
+```
+
+Комментарий: позиция заказа в «внешнем» разрезе; включает и выбранное, и принятое количество.
+
+### OrderDeliveryExt
+
+```
+OrderDeliveryTypeEnum Type //Тип доставки
+decimal Price //Стоимость доставки
+Coordinates Pin //Координаты точки доставки/самовывоза
+string Address //Адрес
+DeliveryInfo DeliveryInfo //Расчётная информация по доставке
+```
+
+Комментарий: снимок параметров доставки, включая результат тарифного расчёта.
+
+### OrderPaymentExt
+
+```
+OrderPaymentTypeEnum Type //Способ оплаты
+string PaymentGate //Платёжный шлюз
+OrderPaymentDetailsExt Payment //Платёж (сумма/идентификаторы/статус)
+OrderPaymentDetailsExt Refund //Возврат (если был)
+```
+
+Комментарий: платёжный блок заказа в форме для обмена.
+
+### OrderPaymentDetailsExt
+
+```
+decimal Amount //Сумма
+string PaymentInternalId //Внутренний ид платежа
+string PaymentId //Ид у платёжного провайдера
+string Status //Статус у провайдера
+```
+
+Комментарий: детали конкретного платежа/возврата.
+
+### OrderCustomerExt
+
+```
+long Id //Id пользователя (0 - если гость)
+string Phone //Телефон
+string Email //Email
+string Name //Имя
+string PhotoFile //Идентификатор фото-файла
+DateTimeOffset? BirthDay //Дата рождения (опционально)
+```
+
+Комментарий: «снимок» данных покупателя, прикрепляется к заказу.
+
+### OrdersPairExt
+
+```
+OrderExt OrderOld //Старый заказ
+OrderExt OrderNew //Новый заказ
+```
+
+Комментарий: пара заказов - используется, когда заказ пересоздаётся с изменёнными параметрами.
+
+### OrderUpdateExt
+
+```
+long SiteId //Id заказа на сайте
+string Id //Внешний ид заказа (мастер-система)
+OrderStatusEnum Status //Статус заказа
+string[] Discounts //Применённые скидки
+OrderProductExt[] Products //Состав заказа
+decimal TotalCost //Итоговая стоимость
+long UpdatedUnixTimestampMS //Момент обновления (Unix ms)
+OrderDeliveryExt Delivery //Информация о доставке
+string Comment //Комментарий клиента
+```
+
+Комментарий: «патч» заказа со стороны мастер-системы (состав, статусы, суммы).
+
+### OrderUpdateProductsExt
+
+```
+long SiteId //Id заказа на сайте
+OrderProductExt[] Products //Новый состав товаров
+decimal TotalCost //Итоговая стоимость
+```
+
+Комментарий: обновление только товарных позиций и суммы заказа.
+
+### OrderUpdateStatusExt
+```
+long SiteId //Id заказа на сайте
+string Id //Внешний ид заказа (мастер-система)
+OrderStatusEnum Status //Новый статус заказа
+```
+
+Комментарий: точечное обновление статуса заказа.
+
+### RemainderExt
+```
+string ProductId //Внешний ид продукта
+decimal Value //Остаток
+```
+
+Комментарий: запись об остатке в формате обмена.
+
+### UpdateRemaindersPackExt
+
+```
+long PackId //Идентификатор пакета синхронизации остатков
+RemainderExt[] Remainders //Массив остатков
+```
+
+Комментарий: пакет для полной/частичной синхронизации остатков.
+
+## DB
+
+Базы данных нет. Сервис - адаптер.
+
+[Назад](/index.md)

+ 64 - 0
docs/backend/services/delivery.md

@@ -0,0 +1,64 @@
+# DeliveryService
+
+## Назначение
+
+Сервис рассчитывает возможность и стоимость доставки по заданному тарифу, точке на карте и весу заказа. Также отдаёт «книгу тарифов» (полный справочник тарифных зон и сеток цен).
+
+## Публичные методы
+
+* `GetTariffsBookAsync(): TariffsBook` - вернуть текущую конфигурацию: все тарифы и все зоны.
+* `SearchDeliveryInfoAsync(tariffName, pin, weight): DeliveryInfo` - вычислить зону по координатам, подобрать подходящую «ступень» веса и вернуть цену. Если доставка недоступна - вернётся `DeliveryInfo.NotAvailable` (`IsAvailable = false`).
+
+> Контроль доступа осуществляется из NBSession (как и в других сервисах): вызов выполняется от имени текущего пользователя.
+
+## Модели
+
+### `TariffsBook`
+
+Справочник, который агрегирует:
+
+* `Tariff[] Tariffs` - перечень тарифов (название + «сетка» цен).
+* `TariffZone[] Zones` - перечень геозон (имя + полигон координат).
+
+### `Tariff` и `TariffNet`
+
+* `Tariff.Name` - имя тарифа (например, «Курьер», «Самовывоз-город»).
+* `Tariff.Net[]` - набор правил «до веса X → цена Y» **в привязке к зоне**:
+
+  * `ZoneName` - имя зоны, к которой относится правило,
+  * `UntilWeightKg` - верхняя граница веса (включительно),
+  * `Price` - цена.
+
+### `TariffZone`
+
+* `Name` - идентификатор зоны (например, «City», «RegionA»).
+* `Coordinates[]` - массив точек полигона зоны (для попадания по `pin`).
+
+### `DeliveryConfig`
+
+Конфигурация, из которой формируется «книга тарифов»:
+
+* `Zones[]` - зоны,
+* `Tariffs[]` - тарифы.
+
+### `DeliveryInfo`
+
+Результат поиска:
+
+* `IsAvailable` - доступна ли доставка,
+* `TariffName`, `ZoneName` - что выбрано,
+* `UntilWeightKg` - порог, сработавший по весу,
+* `Price` - расчётная стоимость. 
+
+## Практические рекомендации
+
+* **Единые названия зон**: `Tariff.Net.ZoneName` должен совпадать с `TariffZone.Name` - это ключ связывания. 
+* **Границы веса**: формируйте «сетку» без дырок и перекрытий. Рекомендуемый набор: `[0.5, 1, 2, 5, 10, 20, 999]`.
+* **Полигон зон**: координаты упорядочивайте по обходу (по/против часовой) и закрывайте полигон (первая точка = последняя), если это требуется вашим утилитам попадания.
+* **NotAvailable**: используйте готовую константу для отказов - `DeliveryInfo.NotAvailable`. Это унифицирует ответы.
+
+## Конфигурация
+
+Сервис читает конфиг (зоны + тарифы) из `config.json` в `DeliveryConfig` и формирует `TariffsBook`. Изменение конфигурации осуществляется заменой json файла. Для обновления конфигурации требуется перезагрузка сервиса.
+
+[Назад](/index.md)

+ 282 - 0
docs/backend/services/directory.md

@@ -0,0 +1,282 @@
+# DirectoryService
+## Назначение
+
+DirectoryService - доменный сервис каталога. Отвечает за справочники (категории, бренды), выборки и сортировки по товарам, разрешение внешних идентификаторов в внутренние `productId`, а также приём пакетных обновлений справочников от интеграции (1С/другие источники). Дополнительно предоставляет вспомогательные выборки (случайные товары) и служебные списки внешних файлов, требующих загрузки.
+
+## Публичные методы DirectoryService
+
+### Справочники и внешние данные
+
+* `GetProductIdsFromExt(extIds[]): int[]` - сопоставить внешние идентификаторы товара с внутренними `productId`.&#x20;
+* `GetBrandsAsync(categoryId = 0): Brand[]` - получить бренды (опционально отфильтрованные по категории).&#x20;
+* `GetCategoriesAsync(): Category[]` - получить дерево/список категорий.&#x20;
+* `GetAllProductsExtAsync(): ProductExt[]` - получить полные внешние данные по товарам (для импорта/синхронизаций).&#x20;
+* `GetAllCategoriesExtAsync(): CategoryExt[]` - получить внешние данные по категориям.&#x20;
+
+### Выборки по каталогу
+
+* `QueryProductsIdsByCategoriesAsync(categoriesId[], intersectIds[], withUnpublished): int[]` - вернуть `productId` по категориям; если `intersectIds` непустой, результат ограничивается пересечением; флаг `withUnpublished` управляет включением неопубликованных.&#x20;
+* `QueryProductsIdsByBrandsAsync(brandsId[], intersectIds[], withUnpublished): int[]` - аналогично, но по брендам.&#x20;
+* `QueryProductsAsync(productIds[]): Product[]` - вернуть товары по списку `productId`.&#x20;
+* `GetProductsRandomAsync(count): Product[]` - получить случайную подборку товаров.&#x20;
+* `SortProductsIdsByPriceAsync(productIds[], desc = false): int[]` - отсортировать список `productId` по цене (по возрастанию по умолчанию; `desc = true` - по убыванию).&#x20;
+
+### Обновления и служебные операции
+
+* `UpdateDirectoryAsync(pack): void` - принять пакет обновления справочников (используется интеграцией).&#x20;
+* `GetFileExtIdsToNeedUploadAsync(take): string[]` - вернуть внешние идентификаторы файлов, которые необходимо догрузить (служебно для синхронизации медиа).&#x20;
+
+## Особенности использования
+
+* **Пересечения фильтров:** методы `QueryProductsIdsBy*` умеют ограничивать выдачу множеством `intersectIds` - удобно для постфильтрации, когда уже есть базовый набор `productId`.
+* **Публикация:** флаг `withUnpublished` позволяет явно включать/исключать неопубликованные позиции (для админских сценариев).
+* **Синхронизация:** `UpdateDirectoryAsync` и выборки `*ExtAsync` - контракт обмена с мастер-системой.
+
+## Взаимодействие c другими сервисами
+
+DirectoryService взаимодействует со следующими сервисами:
+
+* BusinessService - (get only) запрашивает данные об остатках товара.
+* SearchService - (set only) отправляет набор данных для поискового индексирования.
+* FileDirectoryService - (set get) резервиет записи для отложенной загрузки и проверяет загруженны ли файлы.
+
+## Модели
+
+Каждая модель данных имеет 2 представления:
+
+* **DBO (DataBase Object)** - минимальная структура, которая соответствует схеме хранения в базе (Id, внешние ключи, простые поля).
+* **Полные модели** - наследники DBO, которые маппятся автоматически (`AutoMapper`) и содержат дополнительные поля: связи (Brand, Attributes), вычисляемые величины (остатки, рейтинг), технические коллекции (файлы для выгрузки, ProductsCount и пр.).
+
+Такой подход позволяет:
+
+  1. Отделять «источник истины» (БД) от бизнес-логики.
+  2. Явно контролировать, какие поля выгружаются наружу.
+  3. Безопасно обновлять модели без прямого доступа к БД.
+
+Полные модели наследуются от соответствующих DBO и получают дополнительные поля (связи, вычисляемые значения, служебные коллекции). Это позволяет хранить в БД «сырые» данные, а наружу отдавать обогащённые объекты.
+
+Далее представлены полные модели. Если интересно, что представляет DBO смотрите ниже таблицы БД.
+
+> Все модели *Ext относятся к сервису Connector1CService. Их описание там. В угоду продуктивности нарушены правила зависимости и Directory производит некоторые *Ext инстансы, хотя описаны они в Connector1CService.
+
+### Product
+
+```
+string ExtId //Внешний ИД (мастер-система)
+int Id //ИД товара
+int CategoryId //ИД категории (группы товаров)
+string Article //Артикул
+string Name //Наименование
+string NameSEO //SEO-наименование (переписывает Name)
+string Description //Описание
+string DescriptionSEO //SEO-описание (переписывает Description)
+string[] BarCodes //Штрих-коды
+int UnitCode //Код единицы измерения по ОКЕИ
+int BrandId //ИД бренда
+FileRecord[] Fotos //Фото-файлы
+FileRecord[] Files //Другие файлы
+decimal Price //Базовая цена
+float WeightKg //Вес, кг
+Volume SizeM //Габариты: ширина/длина/высота в метрах
+float VolumeM //Объём (альтернативное поле)
+VATCodeEnum VATCode //Код НДС
+TradeAgentSignEnum TradeAgentSign //Признак торгового агента
+TradeAgent TradeAgent //Данные торгового агента
+bool IsPublished //Флаг публикации
+bool IsDeleted //Флаг удаления
+long UpdateTimestamp //Таймштамп обновления
+Brand? Brand //Связь: бренд (полная модель)
+ProductAttribute[] Attributes //Связанные атрибуты (полные модели)
+float Rate //Средняя оценка (не хранится в БД)
+decimal Remainder //Остаток на складе (не хранится в БД)
+```
+
+Комментарий: `Product` - «обогащённая» надстройка над `ProductDBO`: кроме полей хранения включает связи (`Brand`, `Attributes`) и витринные показатели (`Rate`, `Remainder`).
+
+### Category
+
+```
+string ExtId //Внешний ИД (мастер-система)
+int Id //ИД категории
+int ParentId //ИД родителя
+int SortIndex //Порядок сортировки
+string Name //Название категории
+string IconSVG //Иконка (SVG)
+long UpdateTimestamp //Таймштамп обновления
+bool IsDeleted //Флаг удаления
+int ProductsCount //Количество товаров в категории (не хранится в БД)
+```
+
+Комментарий: `Category` добавляет счётчик товаров `ProductsCount` для витрины/фильтров; базовые поля приходят из `CategoryDBO`.
+
+### Brand
+
+```
+string ExtId //Внешний ИД (мастер-система)
+int Id //ИД бренда
+string Name //Название бренда
+FileRecord LogoFile //Логотип бренда (файл)
+long UpdateTimestamp //Таймштамп обновления
+bool IsDeleted //Флаг удаления
+```
+
+Комментарий: Полная модель `Brand` повторяет поля DBO и используется как связанная сущность в `Product`; логотип хранится как `FileRecord`.
+
+### ProductAttribute
+
+```
+int ProductId //ИД товара
+int AttributeTypeId //ИД типа атрибута
+string Value //Значение атрибута
+long UpdateTimestamp //Таймштамп обновления
+ProductAttributeType Type //Тип атрибута (полная модель)
+```
+
+Комментарий: В отличие от DBO, здесь сразу есть связанный `Type` с метаданными атрибута; пара (ProductId, AttributeTypeId) уникальна.
+
+### ProductAttributeType
+
+```
+string ExtId //Внешний ИД (мастер-система)
+int Id //ИД типа атрибута
+int CategoryId //ИД категории, к которой относится тип
+ProductAttributeTypeEnum TypeEnum //Семантика (числовой/строковый и т. п.)
+string Name //Название атрибута
+int SortIndex //Порядок сортировки
+long UpdateTimestamp //Таймштамп обновления
+bool IsDeleted //Флаг удаления
+```
+
+Комментарий: Тип атрибута задаёт метаданные для значений, по нему собираются фильтры/характеристики в карточке товара.
+
+### FileRecord
+
+```
+string ExtId //Внешний ИД файла (в мастер-системе)
+string Id //ИД загруженного файла (FilesService)
+string Category //Категория файла (для правил хранения/превью)
+string Tag //Тег/назначение (например, 'main', 'manual')
+string Format //Формат/расширение или дополнительная помета
+```
+
+Комментарий: `FileRecord` хранится как JSON внутри сущностей каталога (товары, бренды) и связывает их с файловым сервисом.
+
+### QueryResult
+
+```
+long Id //Идентификатор сохранённого запроса
+int UserId //ИД пользователя
+string Query //Текст поискового запроса
+string[] LastQueries //Последние запросы пользователя (размер настраивается)
+DateTimeOffset Created //Когда создан результат
+DateTimeOffset ActualUntil //Актуальность результата (до какого времени)
+Category[] Categories //Категории по результатам запроса
+Product[] Products //Товары по результатам запроса
+int ProductsTotal //Общее число товаров по запросу
+```
+
+Комментарий: Служебная модель для отдачи результатов поиска с «памятью» последних запросов и порцией витринных данных.
+
+### TradeAgent
+
+```
+string Inn //ИНН торгового агента
+string OrgName //Название организации
+string Phone //Контактный телефон
+string Address //Адрес организации
+```
+
+Комментарий: TradeAgent - вложенный объект внутри Product, содержит основные реквизиты организации-продавца. Эти данные не лежат в товарной таблице напрямую, а подгружаются и маппятся в модель при синхронизации.
+
+## DB
+
+Содержит таблицы и индексы
+
+```
+-- Migration 1
+pack_sync (
+    Id INTEGER PRIMARY KEY,
+    LastPackId INTEGER NOT NULL DEFAULT 0,
+    LastPackTimestamp INTEGER NOT NULL DEFAULT 0
+);
+
+product_attribute_types (
+    Id INTEGER PRIMARY KEY AUTOINCREMENT,
+    ExtId TEXT NOT NULL DEFAULT '',
+    CategoryId INTEGER NOT NULL DEFAULT 0,
+    TypeEnum INTEGER NOT NULL DEFAULT 0,
+    Name TEXT NOT NULL DEFAULT '',
+    SortIndex INTEGER NOT NULL DEFAULT 0,
+    IsRange INTEGER NOT NULL DEFAULT 0,
+    UpdateTimestamp INTEGER NOT NULL DEFAULT 0
+);
+
+brands (
+    Id INTEGER PRIMARY KEY AUTOINCREMENT,
+    ExtId TEXT NOT NULL DEFAULT '',
+    Name TEXT NOT NULL DEFAULT '',
+    LogoFile TEXT NOT NULL DEFAULT '', 
+    UpdateTimestamp INTEGER NOT NULL DEFAULT 0
+);
+
+categories (
+    Id INTEGER PRIMARY KEY AUTOINCREMENT,
+    ExtId TEXT NOT NULL DEFAULT '',
+    ParentId INTEGER NOT NULL DEFAULT 0,
+    SortIndex INTEGER NOT NULL DEFAULT 0,
+    Name TEXT NOT NULL DEFAULT '',
+    IconSVG TEXT NOT NULL DEFAULT '',
+    UpdateTimestamp INTEGER NOT NULL DEFAULT 0
+);
+
+product_attributes (
+    ProductId INTEGER NOT NULL,
+    AttributeTypeId INTEGER NOT NULL,
+    Value TEXT NOT NULL DEFAULT '',
+    UpdateTimestamp INTEGER NOT NULL DEFAULT 0,
+    PRIMARY KEY (ProductId, AttributeTypeId)
+);
+
+products (
+    Id INTEGER PRIMARY KEY AUTOINCREMENT,
+    ExtId TEXT NOT NULL DEFAULT '',
+    CategoryId INTEGER NOT NULL DEFAULT 0,
+    Article TEXT NOT NULL DEFAULT '',
+    Name TEXT NOT NULL DEFAULT '',
+    NameSEO TEXT NOT NULL DEFAULT '',
+    Description TEXT NOT NULL DEFAULT '',
+    DescriptionSEO TEXT NOT NULL DEFAULT '',
+    BarCodes TEXT NOT NULL DEFAULT '[]', 
+    UnitCode INTEGER NOT NULL DEFAULT 0,
+    BrandId INTEGER NOT NULL DEFAULT 0,
+    Fotos TEXT NOT NULL DEFAULT '[]',   
+    Files TEXT NOT NULL DEFAULT '[]',   
+    Price TEXT NOT NULL DEFAULT 0,
+    WeightKg REAL NOT NULL DEFAULT 0,
+    SizeM TEXT NOT NULL DEFAULT '{}',  
+    VolumeM REAL NOT NULL DEFAULT 0,  
+    IsPublished INTEGER NOT NULL DEFAULT 0,
+    UpdateTimestamp INTEGER NOT NULL DEFAULT 0
+)
+```
+
+```
+-- Migration 2
+-- Add IsDeleted
+
+ALTER TABLE product_attribute_types ADD IsDeleted BOOLEAN NOT NULL DEFAULT FALSE;
+ALTER TABLE brands ADD IsDeleted BOOLEAN NOT NULL DEFAULT FALSE;
+ALTER TABLE categories ADD IsDeleted BOOLEAN NOT NULL DEFAULT FALSE;
+ALTER TABLE products ADD IsDeleted BOOLEAN NOT NULL DEFAULT FALSE;
+```
+
+```
+-- Migration 3
+-- Add VatCode+Comissioner
+
+ALTER TABLE products ADD VATCode INTEGER NOT NULL DEFAULT 0;
+ALTER TABLE products ADD TradeAgentSign INTEGER NOT NULL DEFAULT 0;
+ALTER TABLE products ADD TradeAgent TEXT NOT NULL DEFAULT '{}';
+```
+
+[Назад](/index.md)

+ 48 - 0
docs/backend/services/favorite.md

@@ -0,0 +1,48 @@
+# FavoriteService
+
+## Назначение
+
+Сервис управляет избранными товарами пользователей. Предоставляет базовые CRUD-операции: добавление, удаление, получение списка. Используется клиентской PWA (личный кабинет, карточки товаров) для формирования «Избранного» и быстрых переходов к сохранённым товарам. Контроль доступа основан на данных **NBSession** - то есть все действия выполняются от имени текущего авторизованного пользователя.
+
+## Публичные методы
+
+* `SetFavoriteProductAsync(productId): void` - добавить товар в избранное для текущего пользователя.
+* `UnsetFavoriteProductAsync(productId): void` - удалить товар из избранного.
+* `SubsetFavoriteProductsFromListAsync(userId, productIds[]): int[]` - проверить, какие из переданных товаров находятся в избранном; вернуть массив совпавших `productId`.
+* `GetAllFavoriteProductsAsync(userId): int[]` - получить все избранные товары пользователя.
+
+> Внешний API позволяет работать с `userId`, однако фактический контроль (право получить/изменить избранное) обеспечивается сессией (NBSession). Это защищает от прямого доступа к чужим спискам. Таким образом, пользователь может запросить только данные по себе, если у него нет доступов. Если же есть доступы ([AccessTypeEnum](/common/accessTypeEnum.md).FavoritesView) в NBSession, то можно укажать чужой userId.
+
+## Модели
+
+### FavoritePairDBO
+
+Простейшая сущность для хранения связки «пользователь - товар». Используется в базе/репозитории:
+
+* `UserId` *(long)* - идентификатор пользователя.
+* `ProductId` *(int)* - идентификатор товара.
+
+Эта модель позволяет хранить и искать избранное по составному ключу (UserId + ProductId).
+
+## Типовой поток
+
+1. Пользователь нажимает «добавить в избранное» на карточке товара.
+2. PWA вызывает `SetFavoriteProductAsync(productId)`.
+3. Сервис создаёт запись `FavoritePairDBO(UserId, ProductId)` и сохраняет её.
+4. При запросе списка (`GetAllFavoriteProductsAsync`) сервис возвращает все ProductId, закреплённые за текущим пользователем.
+5. Для массовой проверки используется `SubsetFavoriteProductsFromListAsync` (например, при отрисовке каталога отметить «сердечками» сохранённые товары).
+
+## DB
+
+Содержит таблицы и индексы
+
+```
+-- Migration 1
+favorites (
+    UserId INTEGER NOT NULL,
+    ProductId INTEGER NOT NULL,
+    PRIMARY KEY (UserId, ProductId)
+);
+```
+
+[Назад](/index.md)

+ 144 - 0
docs/backend/services/filesDirectory.md

@@ -0,0 +1,144 @@
+# FilesDirectoryService
+
+## Назначение
+
+Сервис отвечает за хранение и раздачу файлов/медиа, валидацию категорий и параметров, а также за сценарии «отложенной загрузки» через резервирование токенов (для загрузки файлов от 1С). Предоставляет низкоуровневые операции (пути/метаданные/удаление) и высокоуровневые (загрузка, резервирование, превью по конфигу).
+
+## Публичные методы
+
+### Пути/файлы/метаданные
+
+* `GetFilePath(fileId): string` - получить физический путь хранения.
+* `DeleteFile(fileId): void` - удалить файл.
+* `ValidExistFile(fileId): bool` - проверить существование файла.
+* `GetFileInfo(fileId): FileDescriptor` - базовая информация по файлу.
+* `GetFileInfoByHashAndCategory(hash, category): FileDescriptor` - получить файл по хэшу и категории (для дедупликации).
+* `GetFileConfig(category): FileImageConfig` - конфигурация обработки/хранения для категории.
+
+### Загрузка
+
+* `UploadFile(category, stream): FileDescriptor` - загрузить файл в указанную категорию, вернуть дескриптор.
+* `ValidExistFileAndCategoryAsync(filesIds, category): bool` - проверить, что все файлы существуют и принадлежат категории.
+
+### Резервирование и отложенная загрузка
+
+* `ReserveUploadFilesAsync(reserves[]): FileUploadReserve[]` - зарезервировать параметры будущих файлов и выдать токены/идентификаторы резерва.
+* `QueryReservedFileAsync(reserveIds[]): FileDescriptor?[]` - узнать, загружен ли файл по резервации.
+* `UploadReservedFileAsync(reserveTokenId, stream): FileDescriptor` - загрузить файл по ранее выданному токену.
+
+## Конфигурация сервиса
+
+### Модель конфигурации
+
+* `FilesServiceConfig` - корневые параметры: каталог хранилища и массив конфигов по категориям.
+* `FileImageConfig` - правила обработки по категории: JPEG-качество, лимиты размеров/веса, ресайзы, набор превью, MIME, доступы. Также может содержать вложенные `Previews` - каскад настроек для генерации производных изображений.
+
+Ключевые поля `FileImageConfig`:
+
+* `Quality` - качество изображения при сохранении;
+* `ResizeToWidth` / `ResizeToHeight` / `MaxSideSize` / `ResizeToWH` - правила изменения размеров (с приоритетами);
+* `MaxFileSize` - ограничение размера исходного файла;
+* `Directory` - подкаталог хранения для категории;
+* `Mime` - ожидаемый MIME тип;
+* `Previews[]` - список правил для генерации превью;
+* `Accesses[]` - список доступов, которым разрешена работа с категорией (если используется контроль доступа на уровне медиа).
+
+### Пример конфигурации (фрагменты)
+
+В `config.json` (приложен к коду, как пример) заданы категории `Product`, `UserIcon`, `Brand`, `Article`, `ArticleTile`, `Review` - каждая со своими лимитами, директориями и превью:
+
+* `Product`: `MaxSideSize: 2048`, превью 256/512, `Mime: image/jpeg`.
+* `UserIcon`: обрезка/ресайз до `ResizeToWH: [512, 512]`.
+* `Article` и `ArticleTile`: с `Accesses: [100]` (пример ограничения по доступам).
+* `Review`: `MaxSideSize: 1920` и превью `128x128`.
+  Корневой каталог хранилища - `FilesStorageDirectory: "filesstorage"`.
+
+## Модели
+
+### `FileDescriptor`
+
+```
+string Id - идентификатор файла, он же название файла
+string Hash - SHA256
+string Category - категория файла
+FileTypeEnum Type - тип файла (пока используется только Image = 1)
+string Mime 
+DateTime Created
+string Attributes - дополнительная информация о файле 
+```
+
+### `FileUploadReserve`
+
+Модель резервации: `ReserveId`, `TokenId` (для последующей загрузки), `Category`, `FileId` (заполняется, если файл уже загружен).
+
+Используется для отложенной загрузки файла от мастер-системы. 
+
+## Типовые потоки
+
+```
+[DirectoryService]   --v
+                       
+[Connector1CService] -> [FilesDirectoryService] -> [Static Files] <- [nginx] <-> PWA
+  ^
+[1C]                 --^  
+```
+
+### Получения файла
+
+Для получения файла нужно знать его категорию и его Id. Балансировочный сервер настраивается на доступ к физическим файлам по ссылке (корневой каталог, который указан в конфиге)/(Название категории)/(ID файла). Таким образом можно обращаться напрямую к статике минуя сервисы. 
+
+### Прямая загрузка
+
+1. Клиент (обычно админская PWA) выбирает категорию → 2) вызывает `UploadFile(category, stream)` → 3) сервис валидирует MIME/размер/правила категории, применяет ресайз/качество, создаёт превью → 4) возвращает `FileDescriptor` (ID, MIME, Category и т. д.). 
+
+### Отложенная загрузка (резервирование)
+
+1. Сервис/Пользователь запрашивает токен через `ReserveUploadFilesAsync`
+2. Позже, когда есть возможность, выполняется `UploadReservedFileAsync(token, stream)`;
+3. Статус можно опрашивать через `QueryReservedFileAsync(reserveIds)`;
+4. После успешной загрузки `FileId` становится доступным в модели резерва и можно его обновить у инициатора.
+
+Так Directory синхронизируя данные с мастер системой, может зарезервировать файлы на загрузку. В качестве TokenId указывается Id файла из мастер системы. Данные о товарах загружаются большим скопом и быстро, а файлы загружаются долго. Поэтому приходится резервировать место для файлов, чтобы мастер система могла потом сама загрузить нужные файлы в FilesDirectory, когда будет возможность. Directory по заданию опрашивает FilesDirectory на предмет загрузки зарезервированных файлов, и обновляет информацию у себя.
+
+---
+
+## Валидация, инварианты и безопасность
+
+* **Соответствие категории:** операции `ValidExistFileAndCategoryAsync` и `GetFileInfoByHashAndCategory` защищают от подмены категории/хэша и ошибок склейки.
+* **Лимиты и преобразования:** применяются по `FileImageConfig` с приоритетами правил ресайза (ширина → высота → max side → точный WH).
+* **Доступы к категориям:** при наличии `Accesses[]` доступ ограничивается доступами/ролями (пример - статьи/тайлы). Это позволяет хранить служебные медиа, недоступные обычным пользователям.
+* **Дедупликация:** поиск по `Hash + Category` позволяет повторно использовать уже загруженный файл.
+
+---
+
+Если нужно, оформлю это как отдельный раздел в документе и добавлю таблицу категорий из `config.json` (категория → директория → лимиты → превью → доступы).
+
+
+## DB
+
+Содержит таблицы и индексы
+
+```
+-- Migration 1
+
+files (
+    Id TEXT PRIMARY KEY NOT NULL, 
+    Category TEXT NOT NULL DEFAULT '',
+    Path TEXT NOT NULL DEFAULT '', -- физический путь к файлу
+    Hash TEXT NOT NULL DEFAULT '',
+    Type INTEGER,
+    Mime TEXT NOT NULL DEFAULT '',
+    Created NUMBER,
+    CreatorId NUMBER,
+    Attributes TEXT);
+
+reserves (
+    ReserveId INTEGER PRIMARY KEY AUTOINCREMENT,
+    TokenId TEXT NOT NULL DEFAULT '',
+    Category TEXT NOT NULL DEFAULT '',
+    FileId TEXT NOT NULL DEFAULT '');
+
+INDEX files_hash on files (hash);
+```
+
+[Назад](/index.md)

+ 103 - 0
docs/backend/services/headhunt.md

@@ -0,0 +1,103 @@
+# HeadHuntService
+
+## Назначение
+
+Сервис вакансий и откликов. Позволяет публиковать и редактировать предложения по работе (офферы), управлять их статусами (публикация, архив), а также обрабатывать отклики кандидатов. Контроль доступа реализуется через NBSession (текущая сессия пользователя).
+
+## Публичные методы
+
+Админский функционал доступен только для [AccessTypeEnum](/common/accessTypeEnum.md).HeadHuntControl.
+
+* `GetWorkOffersAsync(onlyIsPublished = true): WorkOffer[]` - вернуть список доступных вакансий. Если `onlyIsPublished == false`, доступно только администратору.
+* `GetWorkOfferAsync(offerId): WorkOfferDetails` - получить одну вакансию по идентификатору.
+* `PostWorkOfferResponseAsync(response): WorkOfferResponse` - оставить отклик кандидата.
+
+Все следующие методы исключительно для администрации:
+
+* `UpdateWorkOfferAsync(offer): WorkOffer` - создать (если `Id == 0`) или обновить вакансию (редактируются только определённые поля).
+* `SetPublishedAsync(offerId, isPublished): void` - включить или выключить публикацию вакансии.
+* `ArchiveOfferAsync(offerId): void` - перевести вакансию в архив.
+* `GetActiveWorkOfferResponsesAsync(forOfferId): WorkOfferResponse[]` - получить список откликов по вакансии.
+* `ProcessWorkOfferResponseAsync(responseId): void` - обработать отклик (например, подтвердить или отклонить).
+
+## Модели
+
+### WorkOffer
+
+```
+int Id – идентификатор вакансии
+string Header – заголовок оффера
+string City – город
+string[] SubHeaderInfo – краткая дополнительная информация через запятую
+string EmploymentType – тип занятости (полная, частичная и т. д.)
+string Schedule – график работы
+string Experience – требуемый опыт
+string Salary – зарплата
+string DescriptionHTML – полное описание вакансии
+string Contacts – блок контактов
+string[] Tags – набор тегов
+bool IsPublished – признак публикации
+bool IsDeleted – признак удаления
+DateTimeOffset Created – дата создания
+```
+
+### WorkOfferResponse
+
+```
+long Id – идентификатор отклика
+int OfferId – идентификатор вакансии, на которую отклик
+string Name – имя кандидата
+string Phone – телефон кандидата
+string Email – email кандидата
+string Comment – комментарий/сопроводительное письмо
+string HHLink – ссылка на профиль в HeadHunter или резюме
+bool IsProcessed – признак, что отклик обработан
+DateTimeOffset Created – дата создания отклика
+```
+
+### WorkOfferDetails
+
+```
+WorkOffer WorkOffer – вакансия
+int ResponsesCount – общее число откликов
+int ResponsesProcessedCount – число обработанных откликов
+```
+
+
+## DB
+
+Содержит таблицы и индексы
+
+```
+-- Migration 1
+offers (
+    Id INTEGER PRIMARY KEY AUTOINCREMENT,
+    Header TEXT NOT NULL,
+    City TEXT NOT NULL,
+    SubHeaderInfo TEXT NOT NULL,
+    EmploymentType TEXT NOT NULL,
+    Schedule TEXT NOT NULL,
+    Experience TEXT NOT NULL,
+    Salary TEXT NOT NULL,
+    DescriptionHTML TEXT NOT NULL,
+    Contacts TEXT NOT NULL,
+    Tags TEXT NOT NULL,
+    IsPublished BOOLEAN NOT NULL DEFAULT FALSE,
+    IsDeleted BOOLEAN NOT NULL DEFAULT FALSE,
+    Created NUMBER NOT NULL
+);
+
+responses (
+    Id INTEGER PRIMARY KEY AUTOINCREMENT,
+    OfferId INTEGER NOT NULL,
+    Name TEXT NOT NULL,
+    Phone TEXT NOT NULL,
+    Email TEXT NOT NULL,
+    Comment TEXT NOT NULL,
+    HHLink TEXT NOT NULL,
+    IsProcessed BOOLEAN NOT NULL DEFAULT FALSE,
+    Created NUMBER NOT NULL
+)
+```
+
+[Назад](/index.md)

+ 100 - 0
docs/backend/services/profiles.md

@@ -0,0 +1,100 @@
+# ProfilesService
+
+## Назначение
+
+Сервис управляет пользователями платформы: созданием, поиском, изменением контактных данных (телефон/email), базовыми профайлами (имя, фото, дата рождения) и их доступами. Контроль доступа - по данным текущей сессии (NBSession). Сервис предоставляет единый источник истины по пользовательским записям для остальных доменов.
+
+## Публичные методы
+
+* `GetMyUserAsync(): User` - получить текущего пользователя по сессии; при отсутствии - исключение.
+
+### Чтение пользователей
+
+* `GetUserAsync(userId): User` - получить пользователя по Id (исключение, если не найден).
+* `GetUsersAsync(userIds[]): User[]` - получить список пользователей по Id.
+* `GetUserOrNullAsync(userId): User?` - получить пользователя или `null`, если не найден.
+
+### Поиск
+
+* `GetUserOrNullByPhoneAsync(phone): User?` - найти пользователя по номеру телефона.
+* `SearchUsersAsync(dataParted): User[]` - поиск по части имени или телефона.
+* `SearchUsersByAccessesAsync(include[], exclude[], includeWithNoAccess): User[]` - фильтрация по доступам (с опцией включать бездоступных).
+* `GetUsersWithAccessesAsync(): User[]` - выборка всех пользователей с какими-либо доступами.
+
+### Создание и удаление
+
+* `CreateUserByPhoneAsync(phone): User` - создать пользователя по номеру телефона.
+* `DeleteUserAsync(userId): void` - пометить пользователя как удалённого.
+
+### Редактирование профиля
+
+* `SetUserData(user: UserData): User` - обновить данные профиля (имя, фото, дата рождения).
+* `ChangeUserPhoneAsync(userId, phone): User` - изменить телефон.
+* `ChangeUserEmailAsync(userId, email): User` - изменить email.
+
+### Управление доступами
+
+* `SetUserAccess(userId, access): void` - добавить доступ.
+* `UnsetUserAccess(userId, accessType): void` - удалить доступ.
+* `SetUserAllAccesses(userId, accesses[]): void` - заменить полный набор доступов.
+
+## Взаимодействие с другими сервисами
+
+ProfilesService не является инициатором взаимодействий с другими сервисами. Другие сервисы (как и пользователи), у которых есть соответвующие права доступа могут вызывать любые методы.
+
+Справочно: AuthService может создавать пользователя, получать данные пользователя, менять номер телефона.
+
+## Модели
+
+### User
+
+```
+long Id //Идентификатор, ключевое поле
+DateTimeOffset Created //Когда создан
+bool IsDeleted //Флаг удаления
+string Name //Имя пользователя
+string PhotoFile //Наименование файла иконки
+DateTimeOffset? BirthDay //Дата рождения
+Authenticator[] Auths //Набор аутентификаторов (телефон, емайл), более подробнее см. код
+Access[] Accesses //Набор доступов ([доступ](/common/accessTypeEnum.md) и на сколько выдан)
+```
+
+### UserData
+
+Лёгкая DTO для обновления основных полей пользователя: `Id`, `Name`, `PhotoFile`, `BirthDay`. Содержит фабрику `FromUser(User user)` для удобного маппинга из полной модели. Используется в API `SetUserData(UserData user)`.
+
+```
+long Id //Идентификатор, ключевое поле
+string Name //Имя пользователя
+string PhotoFile //Наименование файла иконки
+DateTimeOffset? BirthDay //Дата рождения
+```
+
+---
+
+# Поведение и инварианты
+
+* **Единый источник истины:** все изменения профиля выполняются через ProfilesService; другие сервисы не модифицируют `User` напрямую.
+* **Idempotency при регистрации по телефону:** вызов пары `GetUserOrNullByPhoneAsync` → `CreateUserByPhoneAsync`, как в AuthService, гарантирует отсутствие дублей по номеру.
+* **Мягкое удаление:** `DeleteUserAsync` помечает пользователя как удалённого (`IsDeleted = true` в модели), что позволяет сохранять историю операций и ссылочную целостность. (Поле `IsDeleted` есть в `User`.)
+* **Актуальность контактов:** `ChangeUserPhoneAsync`/`ChangeUserEmailAsync` обновляют каналы аутентификации и связи, чтобы `GetPhone()`/`GetEmail()` возвращали корректные данные.
+
+## DB
+
+Содержит таблицы и индексы
+
+```
+-- Migration 1
+users (
+    Id INTEGER PRIMARY KEY AUTOINCREMENT,
+    Created NUMBER NOT NULL,
+    IsDeleted BOOLEAN NOT NULL DEFAULT FALSE,
+    Name TEXT NOT NULL,
+    PhotoFile TEXT NOT NULL,
+    BirthDay NUMBER DEFAULT NULL,
+    Auths TEXT NOT NULL, 
+    Accesses TEXT NOT NULL 
+)
+```
+
+[Назад](/index.md)

+ 143 - 0
docs/backend/services/reviews.md

@@ -0,0 +1,143 @@
+# ReviewsService
+
+## Назначение
+
+Сервис управляет пользовательским контентом по товарам: отзывами и вопросами. Поддерживает публикацию сообщений (текст + фото), модерацию, ответы на вопросы, а также систему лайков/дизлайков. Дополнительно формирует агрегированную аналитику по товарам (средняя оценка, количество отзывов, лучшие отзывы, последние 10 и список вопросов). Контроль доступа осуществляется через NBSession.
+
+Публикуются контент только прошедший модерацию.
+
+Далее отзыв и вопрос, если не имеет принципиальной разницы называется сообщением.
+
+Для администрирования необходимо иметь доступ [AccessTypeEnum](/common/accessTypeEnum.md).ReviewsQuestionsControl.
+
+## Публичные методы
+
+* `GetMyReviewsAndQuestionsAsync(skip = 0, take = 50): ProductMessage[]` - вернуть список сообщений, созданных текущим пользователем (отзывы и вопросы).
+* `GetReviewsReportAsync(productId): ProductReviewsReport` - агрегированный отчёт по отзывам для товара.
+* `GetQuestionsReportAsync(productId): ProductQuestionsReport` - агрегированный отчёт по вопросам для товара.
+* `GetRatesAsync(productIds[]): float[]` - массив средних оценок для списка товаров.
+* `GetReviewsAsync(productId, skip = 0, take = 10): ProductMessage[]` - список отзывов по товару с постраничной выборкой.
+* `GetReviewsToApprove(skip = 0, take = 10): ProductMessage[]` - список отзывов, ожидающих модерации.
+* `GetQuestionsToApprove(skip = 0, take = 10): ProductMessage[]` - список вопросов, ожидающих модерации.
+* `GetMessageAsync(id): ProductMessageFull` - сообщение по идентификатору.
+* `ApproveProductMessageAsync(messageId, answerText): void` - подтвердить сообщение (модерация) и при необходимости добавить текст ответа.
+* `DeleteProductMessageAsync(messageId): void` - удалить сообщение (отзыв или вопрос).
+* `PostReviewAsync(review): ProductMessage` - опубликовать новый отзыв.
+* `PostQuestionAsync(question): ProductMessage` - опубликовать новый вопрос.
+* `ReactOnMessageAsync(messageId, isLike): ProductMessage` - поставить реакцию (лайк или дизлайк) на сообщение и вернуть обновлённое.
+
+## Особенности и правила
+
+* **Типы сообщений**: `ProductMessage.Type` определяет отзыв или вопрос; у отзывов может быть `Rate`, у вопросов - `Rate=0`.
+* **Связи**: поле `ReplyToId` позволяет строить дерево (вопрос → ответ), массив `RepliesIds` хранит идентификаторы ответов.
+* **Модерация**: поле `IsApproved` устанавливается администратором. Только подтверждённые сообщения видны обычным пользователям.
+* **Агрегированные отчёты**:
+
+  * Отзывы: средняя оценка (`RateMiddle`), количество (`ReviewsCount`), все фото (`ReviewsAllFotos`), лучший отзыв (`BestReview`), последние 10 (`ReviewsLast10`).
+  * Вопросы: количество (`QuestionsCount`), список всех вопросов с ответами (`QuestionsAll`).
+* **Лайки и дизлайки**: модель `LikesRecord` фиксирует реакцию пользователя (`TargetId`, `UserId`, `Value`). Один пользователь может иметь только одну реакцию на сообщение.
+* **Контроль доступа**: пользователь может удалять/редактировать только свои сообщения; модераторы - любые. NBSession гарантирует проверку авторства и прав.
+
+## Взаимодействие c другими сервисами
+
+```
+                     / [ProfilesService]
+[ReviewsService] <-----[DirectoryService]
+                     \ [FilesDirectoryService]
+```
+
+ReviewsService запрашивает данные (get only) у следующих сервисов:
+
+* ProfileService - запрос имени пользователя, если тот не указал иное при создании контента.
+* DirectoryService - запрос на проверку существования и наименования товара, по которому создается контент.
+* FileDirectoryService - проверка на наличие указанных файлов (прикрипленных фото).
+
+## Модели
+### ProductMessage
+
+```
+ProductMessageTypeEnum Type – тип сообщения (отзыв или вопрос)
+long Id – идентификатор сообщения
+long ReplyToId – идентификатор сообщения, на которое дан ответ (0, если это не ответ)
+long[] RepliesIds – список идентификаторов ответов
+int ProductId – идентификатор товара
+string ProductName – название товара
+long OwnerId – идентификатор пользователя, оставившего сообщение
+string OwnerName – имя пользователя
+string Text – текст сообщения
+int Rate – оценка (1–5 для отзывов, 0 для вопросов)
+string[] Fotos – список ссылок на фотографии, прикреплённые к сообщению
+int LikesCount – количество лайков
+int DislikesCount – количество дизлайков
+DateTimeOffset Created – дата и время создания сообщения
+bool IsApproved – признак того, что сообщение прошло модерацию
+```
+
+### ProductReviewsReport
+
+```
+int ProductId – идентификатор товара
+float RateMiddle – средняя оценка товара по отзывам
+int ReviewsCount – количество отзывов
+string[] ReviewsAllFotos – все фотографии из отзывов по товару
+ProductMessage? BestReview – лучший отзыв
+ProductMessage[] ReviewsLast10 – последние 10 отзывов
+```
+
+### ProductQuestionsReport
+
+```
+int ProductId – идентификатор товара
+int QuestionsCount – количество вопросов по товару
+ProductMessage[] QuestionsAll – список всех вопросов с привязанными ответами
+```
+
+### LikesRecord
+
+```
+long TargetId – идентификатор сообщения, к которому относится лайк/дизлайк
+int UserId – идентификатор пользователя, оставившего реакцию
+int Value – значение реакции (1 = лайк, -1 = дизлайк)
+```
+
+### ProductMessageTypeEnum
+
+```
+Unknown = 0 – неопределённый тип сообщения
+Review = 1 – отзыв о товаре
+Question = 2 – вопрос о товаре
+```
+
+## DB
+
+Содержит таблицы и индексы
+
+```
+-- Migration 1
+messages (
+    Id INTEGER PRIMARY KEY,
+    Type INTEGER NOT NULL,
+    ReplyToId INTEGER NOT NULL,
+    RepliesIds TEXT NOT NULL DEFAULT '[]',
+    ProductId INTEGER NOT NULL,
+    ProductName TEXT NOT NULL DEFAULT '',
+    OwnerId INTEGER NOT NULL,
+    OwnerName TEXT NOT NULL DEFAULT '',
+    Text TEXT NOT NULL DEFAULT '',
+    Rate INTEGER NOT NULL,
+    Fotos TEXT NOT NULL DEFAULT '[]',
+    LikesCount INTEGER NOT NULL,
+    DislikesCount INTEGER NOT NULL,
+    Created INTEGER NOT NULL,
+    IsApproved BOOLEAN NOT NULL DEFAULT FALSE
+);
+
+likes (
+    TargetId INTEGER NOT NULL,
+    UserId INTEGER NOT NULL,
+    Value INTEGER NOT NULL,
+    PRIMARY KEY (TargetId, UserId)
+)
+```
+
+[Назад](/index.md)

+ 85 - 0
docs/backend/services/search.md

@@ -0,0 +1,85 @@
+# SearchService
+
+## Назначение
+
+SearchService отвечает за индексацию текстовых данных (товары, категории и др.) и быстрый полнотекстовый поиск по ключевым словам. Его задача - по входному запросу вернуть набор идентификаторов товаров максимально быстро, с учётом синонимов, опечаток и типичных ошибок написания. Контроль доступа - через сессию (NBSession).
+
+## Публичные методы
+
+### Синонимы, опечатки, ошибки написания
+
+* `SetSynonymsAsync(pairs: KeywordPair[]): void` - установить список пар синонимов.
+* `GetSynonymsAsync(): KeywordPair[]` - получить актуальный словарь синонимов (например, «шпатлевка → шпаклевка»).
+* `SetTyposAsync(pairs: KeywordPair[]): void` - задать перечень типовых опечаток (символьные замены). Этот список критичен к производительности, его нельзя раздувать.
+* `GetTyposAsync(): KeywordPair[]` - вернуть текущие опечатки (например, «о→а», «с→cc»).
+* `SetMisspellingsAsync(pairs: KeywordPair[]): void` - задать список ошибок написания целых слов. Дубли уже покрытых «typos» не включаются.
+* `GetMisspellingsAsync(): KeywordPair[]` - вернуть словарь ошибок написания (например, «ломинат → ламинат»).
+
+### Индексация и поиск
+
+* `FeedAsync(feeds: FeedPackage[]): void` - передать пакеты данных для индексации (источник, тип, ключевые поля/фразы, метка времени).
+* `SearchProductsIds(query: string, filters: QueryFilter[], sortType: QuerySortTypeEnum): int[]` - выполнить поиск и вернуть идентификаторы товаров, соответствующих запросу и фильтрам, в указанном порядке сортировки.
+
+## Как устроен поиск (высокоуровнево)
+
+1. **Нормализация запроса**: приводим строку к канонической форме; применяем словари `synonyms`, `typos`, `misspellings` (слово-замены и символьные замены). Это расширяет «поле находок» без дублирования.
+2. **Сопоставление с индексом**: для каждой сущности, поданной через `FeedAsync`, в индексе хранятся «ключевые слова» и поля («Keywords»). По ним и производится быстрый матч.
+3. **Применение фильтров**: дополнительно ограничиваем результат по `QueryFilter` (категория, бренд, «в наличии» и типовые атрибуты), см. ниже.
+4. **Сортировка**: порядок задаётся параметром `sortType` (тип перечисления определён в контракте `QuerySortTypeEnum`).
+
+## Фильтры (QueryFilter)
+
+`QueryFilter` состоит из `FilterTypeId` и набора `Values`. По умолчанию `FilterTypeId` - это **идентификатор типа атрибута товара**, но предусмотрены специальные значения от 2 000 000 000 и выше:
+
+```
+FILTER_CATEGORY = 2_000_000_000  // фильтр по категории
+FILTER_IN_STOCK = 2_000_000_001  // фильтр "в наличии"
+FILTER_BRAND   = 2_000_000_002  // фильтр по бренду
+```
+
+Значения `Values` интерпретируются в контексте типа фильтра (набор id категорий/брендов, булевы значения и т. п.).
+
+## Модели (используемые сервисом)
+
+### `KeywordPair`
+
+```
+string Source //исходная форма (что искать/заменять)
+string Target //целевая форма (на что заменять/добавлять в поиск)
+```
+
+Используется в словарях: `synonyms`, `typos`, `misspellings`.
+
+### `FeedPackage`
+
+```
+long Id //идентификатор пакета
+int SourceId //идентификатор источника (например, товара/категории)
+string SourceType //тип источника (строка вместо enum для независимости и сериализации)
+string[] Keywords //ключевые слова/фразы для индексации
+long Timestamp //Unix timestamp (секунды)
+```
+
+Пакеты формируют/обновляют индекс; тип хранится строкой для упрощения сериализации и ослабления связности.
+
+### `QueryFilter`
+
+```
+int FilterTypeId //тип фильтра: id типа атрибута либо специальные константы (см. выше)
+string[] Values //значения фильтра (строки)
+```
+
+Фильтры комбинируются: итоговая выдача - пересечение условий.
+
+## Эксплуатационные заметки
+
+* **Производительность:** список `typos` - самый чувствительный ко времени отклика; держите его минимальным и осмысленным.
+* **Качество поиска:** словари `synonyms` и `misspellings` повышают полноту, но добавляют ветвления - поддерживайте их в актуальном состоянии и избегайте «шумных» правил.
+* **Согласованность с каталогом:** `DirectoryService` обновляем словари в момент получения обновления своей базы и момент первоначальной загрузки. 
+* **Доступ:** операции выполняются от имени текущей сессии; результаты могут в дальнейшем пересекаться с правами публикации/видимости на стороне каталога.
+
+## DB
+
+Не используется.
+
+[Назад](/index.md)

+ 57 - 0
docs/backend/services/support.md

@@ -0,0 +1,57 @@
+# SupportService
+
+## Назначение
+
+Упрощённый сервис технической поддержки: принимает обращения пользователей, отдаёт список тикетов для интерфейсов менеджеров/админки и позволяет закрывать запросы. Контроль доступа - из сессионных данных (NBSession).
+
+## Публичные методы
+
+* `CreateRequestAsync(request): SupportRequest` - создать новый тикет с данными пользователя и сообщением. Возвращает созданную запись.
+* `CloseRequestAsync(requestId): void` - закрыть тикет по идентификатору (устанавливает флаг закрытия и время закрытия).
+* `GetRequestsAsync(onlyActive = true, skip = 0, take = 10): SupportRequest[]` - вернуть список запросов с пагинацией. По умолчанию - только активные (не закрытые).
+
+### Типовые сценарии
+
+1. **Пользовательское обращение:** фронт отправляет форму → `CreateRequestAsync` → запись видна менеджерам.
+2. **Обработка и закрытие:** менеджер решает вопрос → `CloseRequestAsync(id)` → тикет скрывается из «активных».
+3. **Список для админки:** `GetRequestsAsync(onlyActive:true, skip, take)` для инбокса, `onlyActive:false` - архив.
+
+## Модели
+### SupportRequest
+
+Сущность тикета поддержки со встроенной очисткой и валидацией.
+
+```
+int Id – идентификатор запроса в поддержку
+long FromUserId – идентификатор пользователя (если авторизован)
+string Reason – общая причина обращения (например, «Покупка товара»)
+string Message – текст сообщения пользователя
+string UserName – имя пользователя
+string UserEmail – email пользователя
+string UserPhone – телефон пользователя
+bool IsClosed – признак, что обращение закрыто
+DateTimeOffset Created – дата и время создания обращения
+DateTimeOffset Closed – дата и время закрытия обращения
+```
+
+## DB
+
+Содержит таблицы и индексы
+
+```
+-- Migration 1
+requests (
+    Id INTEGER PRIMARY KEY AUTOINCREMENT,
+    FromUserId INTEGER NOT NULL,
+    Reason TEXT NOT NULL,
+    Message TEXT NOT NULL,
+    UserName TEXT NOT NULL,
+    UserEmail TEXT NOT NULL,
+    UserPhone TEXT NOT NULL,
+    IsClosed BOOLEAN NOT NULL DEFAULT FALSE,
+    Created NUMBER NOT NULL,
+    Closed NUMBER DEFAULT NULL
+)
+```
+
+[Назад](/index.md)

+ 58 - 0
docs/description.md

@@ -0,0 +1,58 @@
+# Общее описание
+
+Продукт представляет собой платформу электронной коммерции для строительных и хозяйственных товаров, построенную по микросервисной архитектуре с интеграцией с мастер-системой 1С.
+
+Cостоит из:
+   * Двух PWA‑приложений
+      * Интернет магазин (Клиентское)
+      * Админка
+   * Бэкенда, реализованного как набор специализированных микросервисов, доступных через единую точку входа PlatformAPI (ProfiMall.ServerAPI). 
+   * Мастер‑системы по товарному каталогу, ценам, остаткам и ряду оперативных данных, которой выступает 1С‑сервер.
+   * Балансировочный сервер (nginx)
+
+```
+   [PlatformAPI_0](МС_0, М_1, ...) + [PlatformAPI_N](МС_N+1, М_N+2, ...)
+   _____________________________________________________________________
+                                 ^
+                                 v
+                     [1C] <-> [nginx] <-> [Внешние сервисы]
+                                 ^
+                                / \
+                               v   v
+                     [Shop PWA] [Admin PWA]
+```
+
+## Основные составляющие
+
+### Фронтенд
+
+* **Клиентское приложение (Shop PWA)** — интернет-магазин для покупателей. Поддерживает каталог, поиск, карточку товара, корзину, оформление заказа, личный кабинет, отзывы и избранное.
+* **Административное приложение (Admin PWA)** — рабочий инструмент для сотрудников. Предоставляет функции настройки функционала и конфигураций, публикация статей и новостей, модерации отзывов и работы с обращениями клиентов.
+
+Оба клиента реализованы как Progressive Web Application (PWA). Они взаимодействуют с системой только через Platform API, не обращаясь к микросервисам напрямую.
+
+[Подробнее](/docs/frontend/index.md)
+
+### Бэкенд
+
+Серверная часть выполнена в виде:
+
+* набора микросервисов;
+* агрегирующей платформы Platform API, объединяющей микросервисы внутри одного (или нескольких) программного домена, и предоставляющий доступ через HTTP API к интерфейсам сервисов;
+* статичного набора файлов PWA-приложений
+* балансировщика в виде nginx-сервера;
+
+[Подробнее](/docs/backend/index.md)
+
+### Мастер-система (1С)
+
+1С является источником истины для товарного каталога, цен, остатков и статусов заказов. Она обменивается данными с платформой через специальный сервис-адаптер — Connector1CService, который преобразует данные из формата 1С в формат платформы и обратно. Таким образом достигается совместимость без жёсткой привязки архитектуры к особенностям 1С.
+
+### Балансировочный сервер
+
+В роли балансировочного сервера используется nginx, через который идет все заимодействие между всеми частями системы.
+
+
+[Архитектура решения](/docs/architecture.md)
+
+[Назад](/index.md)

+ 107 - 0
docs/frontend/index.md

@@ -0,0 +1,107 @@
+# Фронтенд
+
+* Клиентское приложение (Shop PWA) — интернет-магазин для покупателей. Поддерживает каталог, поиск, карточку товара, корзину, оформление заказа, личный кабинет, отзывы и избранное.
+* Административное приложение (Admin PWA) — рабочий инструмент для сотрудников. Предоставляет функции настройки функционала и конфигураций, публикация статей и новостей, модерации отзывов и работы с обращениями клиентов.
+
+## Стек
+
+* Проект
+    * TypeScript - язык разработки
+    * idb - кэширование
+    * @ivanlog/gs - фреймворк UI
+    * @ivanlog/net - фреймворк API
+* Для разработки
+    * webpack - упаковка
+    * scss - стилевой предпроцессор
+    * TS.Codegen.OpenAPIv3ToClientTS - кодогенератор TS API клиента
+    * gs.Codegen.Templates - кодогенератор GS UI-шаблонов
+
+## Структура решения
+
+В виду большой части общего кода в обоих приложения они имеют общую кодовую базу. Разделение осуществляется через разную точку входа в код.
+
+- frontend
+    - templates [папка шаблонизатора gs]
+    - site
+        - bin [результаты компиляции]
+        - htdocs_pm [сборка приложения магазина]
+        - htdocs_admin [сборка приложения админки]
+        - src [исходники]
+            - appAdmin
+                - controllers [контроллеры приложения]
+                - states [состояния приложения]
+                - config.ts [конфигурация приложения, точек доступа]
+                - index.ts [точка входа]
+                - AppAdmin.ts
+            - appProfiMall
+                - controllers [контроллеры приложения]
+                - states [состояния приложения]
+                - config.ts [конфигурация приложения, точек доступа]
+                - index.ts [точка входа]
+                - AppPM.ts
+            - classes [общий код]
+
+## Структура кода
+
+Оба приложения имеют схожую структуру, поэтому описание общее.
+
+Код делится на 2 группы:
+* общий код (classes).
+* уникальный код приложения (app*).
+
+В общий код входит:
+
+* api - кодосгенерированная папка с набором инструментов для работы с Profimoll API OpenAPIv3 (готовый клиент и набор всех моделей для работы с API).
+* appControllers - набор абстрактных контроллеров.
+* com - кодосгенерированная папка с набором компонентов gs.Templates + их написанные реализации (для переопределенных компонентов).
+* enums - перечисления.
+* models - общие модели (не API).
+* states - абстрактные состояния.
+* App.ts - абстратный класс приложения.
+* *Tags.ts - перечисления тэгов.
+* Helper*.ts - набор хелперов.
+
+В уникальный код входит:
+
+* controllers - набор котроллеров приложения (GUI, корзина, избранное и так далее).
+* states - набор состояний приложения, в которых и описана основная бизнес логика по состоянию.
+* index.ts - точка входа в программу для компиляции.
+* App*.ts - основной класс приложения, в котором проходит инициализация всех уникальных элементов приложения (подробнее см.код).
+
+
+## Нотации
+
+### Верстка
+
+* Соблюдать нотации gs для разработки паков.
+* Название пака - profimall, сокращение pm
+
+### Документирование
+
+* Использование jsdoc - по усмотрению, в случаях отсутсвия строгой типизации.
+* Использование понятного наименования, которые позволит корректно читать код и его функционал.
+* Обязательные комментарии для сложных блоков кода.
+
+### Контроль версий
+
+Используем Git на https://git.ivanlog.ru/
+
+Репозитории:
+* gs шаблоны - https://git.ivanlog.ru/ProfiMall/Frontend.Templates
+* фронтенд - https://git.ivanlog.ru/ProfiMall/FrontEnd.App
+
+Запрос доступа у ivanlog.
+
+Основная ветка разработки - dev.
+Каждый разработчик заводит свою ветку по виду dev-имя_разработчика. В ней ведет разработку отдельный фич, после мержит в dev.
+Основная ветка деплоя - main.
+
+Шаблон версий для публикации vXX.XX.XX
+
+## Технические требования
+
+* Для разработки - особых требований нет. 
+* Для деплоя - приложения полностью статичны. Необходимо обеспечить доступ к статичным файлам приложения и форвардинг запросов к API в одном домене для соблюдения CORS. 
+* Для использования - современный web-браузер с поддержкой актульных технологий (на 2025 год).
+
+[Назад](/index.md)

+ 26 - 0
docs/legal.md

@@ -0,0 +1,26 @@
+# Лицензии
+
+* Фронтенд
+   * Проект
+      * TypeScript (Apache-2.0)
+      * @ivanlog/gs (MIT)
+      * @ivanlog/net (MIT)
+   * Для разработки
+      * webpack (MIT)
+      * scss (MIT)
+      * TS.Codegen.OpenAPIv3ToClientTS (MIT)
+      * gs.Codegen.Templates (MIT)
+* Бэкэнд
+   * Dapper (Apache-2.0)
+   * AutoMapper (MIT)
+   * SQLite (MIT)
+   * BStorm (MIT)
+   * NetBridge (MIT)
+   * Yandex.Checkout.V3 (MIT)
+   * SixLabors.ImageSharp (особое внимание)
+
+## SixLabors.ImageSharp
+
+Внимательно проверяйте установленную версию. Только версия 2.x.x имеет лицензию Apache-2.0. Более высокие версии - требуют лицензирования.
+
+[Назад](/index.md)

+ 21 - 0
index.md

@@ -0,0 +1,21 @@
+# Техническая документация (Профимолл - интернет магазин)
+
+* [Общее описание](/docs/description.md)
+* [Архитектура проекта](/docs/architecture.md)
+* [Frontend](/docs/frontend/index.md)
+* [Backend](/docs/backend/index.md)
+  * Сервисы
+    * [AuthService](/docs/backend/services/auth.md) - аутентификация и авторизация (телефон, SMS/flash-call, выдача токена, управление сессией).
+    * [ProfilesService](/docs/backend/services/profiles.md) - управление пользователями, профили, доступы, телефоны, email.
+    * [DirectoryService](/docs/backend/services/directory.md) - справочники: категории, бренды, товары, фильтрация и сортировка, синхронизация.
+    * [FilesDirectoryService](/docs/backend/services/profiles.md) - хранение и управление файлами/медиа: загрузка, резервирование токенов, проверка существования.
+    * [SearchService](/docs/backend/services/search.md) - индексация и поиск: работа с синонимами, опечатками, выдача релевантных товаров.
+    * [BusinessService](/docs/backend/services/business.md) - корзина, заказы: создание, отмена, обновления, статусы, взаимодействие с мастер-системой.
+    * [Connector1CService](/docs/backend/services/connector1C.md) - интеграция с 1С: каталоги, остатки, цены, заказы, клиенты, синхронизация.
+    * [FavoriteService](/docs/backend/services/favorite.md) - избранное: добавление/удаление товаров, выборка избранных позиций.
+    * [HeadHuntService](/docs/backend/services/headhunt.md) - вакансии: публикация, редактирование, архивирование офферов, отклики и их обработка.
+    * [ReviewsService](/docs/backend/services/reviews.md) - отзывы и вопросы: постинг, модерация, лайки/дизлайки, отчёты по товарам.
+    * [ArticlesService](/docs/backend/services/articles.md) - публикация статей: создание, обновление, архивирование, публикация, выборка.
+    * [SupportService](/docs/backend/services/support.md) - техподдержка: создание обращений, закрытие, выборка запросов.
+    * [DeliveryService](/docs/backend/services/delivery.md) - тарифы и информация по доставке, расчёт стоимости и сроков.
+* [Лицензии](/docs/legal.md)