initial: server + shared

This commit is contained in:
admin
2026-06-11 13:41:38 +05:00
commit 65da047e7c
148 changed files with 15900 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
export declare const DELIVERY_CARRIERS: readonly ['RUSSIAN_POST', 'OZON_PVZ', 'YANDEX_PVZ', 'FIVE_POST', 'WB_PVZ']
export declare const DELIVERY_CARRIER_LABELS: {
readonly RUSSIAN_POST: 'Почта России'
readonly OZON_PVZ: 'Озон доставка (пункт выдачи)'
readonly YANDEX_PVZ: 'Яндекс доставка (пункт выдачи)'
readonly FIVE_POST: '5Post (пункт выдачи)'
readonly WB_PVZ: 'WB доставка (пункт выдачи)'
}
export declare function deliveryCarrierLabelRu(code: string | null | undefined): string | null
+14
View File
@@ -0,0 +1,14 @@
export const DELIVERY_CARRIERS = Object.freeze(['RUSSIAN_POST', 'OZON_PVZ', 'YANDEX_PVZ', 'FIVE_POST', 'WB_PVZ'])
export const DELIVERY_CARRIER_LABELS = Object.freeze({
RUSSIAN_POST: 'Почта России',
OZON_PVZ: 'Озон доставка (пункт выдачи)',
YANDEX_PVZ: 'Яндекс доставка (пункт выдачи)',
FIVE_POST: '5Post (пункт выдачи)',
WB_PVZ: 'WB доставка (пункт выдачи)',
})
export function deliveryCarrierLabelRu(code) {
if (!code) return null
return DELIVERY_CARRIER_LABELS[code] ?? code
}
+36
View File
@@ -0,0 +1,36 @@
export type NotificationEventType =
| 'order:created'
| 'order:statusChanged'
| 'orderMessage:sent'
| 'orderMessage:adminReply'
| 'payment:statusChanged'
| 'auth:codeRequested'
| 'order:deliveryFeeAdjusted'
export type NotificationChannel = 'email' | 'telegram'
export type NotificationStatus = 'pending' | 'sent' | 'failed'
export const NOTIFICATION_EVENTS: {
ORDER_CREATED: NotificationEventType
ORDER_STATUS_CHANGED: NotificationEventType
ORDER_MESSAGE_SENT: NotificationEventType
ORDER_MESSAGE_ADMIN_REPLY: NotificationEventType
PAYMENT_STATUS_CHANGED: NotificationEventType
AUTH_CODE_REQUESTED: NotificationEventType
DELIVERY_FEE_ADJUSTED: NotificationEventType
}
export const NOTIFICATION_CHANNELS: {
EMAIL: NotificationChannel
TELEGRAM: NotificationChannel
}
export const NOTIFICATION_STATUSES: {
PENDING: NotificationStatus
SENT: NotificationStatus
FAILED: NotificationStatus
}
export const MAX_RETRY_ATTEMPTS: number
export const RETRY_DELAYS_MS: number[]
+26
View File
@@ -0,0 +1,26 @@
/** @typedef {'order:created' | 'order:statusChanged' | 'orderMessage:sent' | 'orderMessage:adminReply' | 'payment:statusChanged' | 'auth:codeRequested'} NotificationEventType */
export const NOTIFICATION_EVENTS = {
ORDER_CREATED: 'order:created',
ORDER_STATUS_CHANGED: 'order:statusChanged',
ORDER_MESSAGE_SENT: 'orderMessage:sent',
ORDER_MESSAGE_ADMIN_REPLY: 'orderMessage:adminReply',
PAYMENT_STATUS_CHANGED: 'payment:statusChanged',
AUTH_CODE_REQUESTED: 'auth:codeRequested',
DELIVERY_FEE_ADJUSTED: 'order:deliveryFeeAdjusted',
}
export const NOTIFICATION_CHANNELS = {
EMAIL: 'email',
TELEGRAM: 'telegram',
}
export const NOTIFICATION_STATUSES = {
PENDING: 'pending',
SENT: 'sent',
FAILED: 'failed',
}
export const MAX_RETRY_ATTEMPTS = 3
export const RETRY_DELAYS_MS = [5_000, 30_000, 120_000]
+17
View File
@@ -0,0 +1,17 @@
export declare const ORDER_STATUSES: readonly [
'DRAFT',
'PENDING_PAYMENT',
'PAID',
'IN_PROGRESS',
'SHIPPED',
'READY_FOR_PICKUP',
'DONE',
'CANCELLED',
]
export type OrderStatus = (typeof ORDER_STATUSES)[number]
export declare const ADMIN_ORDER_TRANSITIONS: Record<string, readonly string[] | { readonly delivery: readonly string[]; readonly pickup: readonly string[] }>
export declare function getNextAdminStatuses(from: string, deliveryType: string): string[]
export declare function canTransitionAdminOrderStatus(order: { status: string; deliveryType: string }, next: string): boolean
+38
View File
@@ -0,0 +1,38 @@
export const ORDER_STATUSES = Object.freeze([
"DRAFT",
"PENDING_PAYMENT",
"PAID",
"IN_PROGRESS",
"SHIPPED",
"READY_FOR_PICKUP",
"DONE",
"CANCELLED",
]);
/**
* Допустимые переходы статусов, доступные админу.
* Значение — массив из next-статусов.
* Для IN_PROGRESS: объект с ключами по deliveryType.
*/
export const ADMIN_ORDER_TRANSITIONS = Object.freeze({
DRAFT: ["PENDING_PAYMENT", "CANCELLED"],
PENDING_PAYMENT: ["PAID", "CANCELLED"],
PAID: ["IN_PROGRESS", "CANCELLED"],
IN_PROGRESS: Object.freeze({
delivery: ["SHIPPED", "CANCELLED"],
pickup: ["READY_FOR_PICKUP", "CANCELLED"],
}),
});
export function getNextAdminStatuses(from, deliveryType) {
const transition = ADMIN_ORDER_TRANSITIONS[from];
if (!transition) return [];
if (Array.isArray(transition)) return [...transition];
return transition[deliveryType] ? [...transition[deliveryType]] : [];
}
export function canTransitionAdminOrderStatus(order, next) {
const from = order.status;
if (from === next) return true;
return getNextAdminStatuses(from, order.deliveryType).includes(next);
}
+1
View File
@@ -0,0 +1 @@
export declare const PAYMENT_METHODS: readonly ['online', 'on_pickup']
+1
View File
@@ -0,0 +1 @@
export const PAYMENT_METHODS = Object.freeze(['online', 'on_pickup'])
+8
View File
@@ -0,0 +1,8 @@
export interface TestChecklistItem {
key: string;
section: string;
action: string;
expectedResult: string;
}
export declare const TEST_CHECKLIST_ITEMS: readonly TestChecklistItem[];
+304
View File
@@ -0,0 +1,304 @@
export const TEST_CHECKLIST_ITEMS = Object.freeze([
// Авторизация
{
key: "auth.register-email",
section: "Авторизация",
action: "Зарегистрироваться по email",
expectedResult: "Код приходит на почту, аккаунт создаётся",
},
{
key: "auth.login-password",
section: "Авторизация",
action: "Войти по паролю",
expectedResult: "Корректный пароль пускает, неправильный — ошибка",
},
{
key: "auth.oauth-vk",
section: "Авторизация",
action: "Войти через OAuth VK",
expectedResult: "Редирект на VK, callback, авторизация успешна",
},
{
key: "auth.oauth-yandex",
section: "Авторизация",
action: "Войти через OAuth Yandex",
expectedResult: "Редирект на Yandex, callback, авторизация успешна",
},
{
key: "auth.reset-password",
section: "Авторизация",
action: "Сбросить пароль",
expectedResult: "Письмо приходит, ссылка работает, пароль меняется",
},
{
key: "auth.logout",
section: "Авторизация",
action: "Выйти из аккаунта",
expectedResult: "Сессия очищается, редирект на страницу входа",
},
// Каталог и товары
{
key: "catalog.homepage",
section: "Каталог и товары",
action: "Открыть главную страницу",
expectedResult: "Слайдер грузится, товары отображаются",
},
{
key: "catalog.filters",
section: "Каталог и товары",
action: "Применить фильтры",
expectedResult: "Фильтры по категории, цене, материалам работают",
},
{
key: "catalog.product-page",
section: "Каталог и товары",
action: "Открыть страницу товара",
expectedResult: 'Фото, описание, цена, кнопка "В корзину" отображаются',
},
{
key: "catalog.seo",
section: "Каталог и товары",
action: "Проверить SEO-метаданные",
expectedResult: "Title, meta, slug корректные",
},
// Корзина
{
key: "cart.add",
section: "Корзина",
action: "Добавить товар в корзину",
expectedResult: "Счётчик корзины обновляется",
},
{
key: "cart.change-qty",
section: "Корзина",
action: "Изменить количество товара",
expectedResult: "Сумма пересчитывается",
},
{
key: "cart.remove",
section: "Корзина",
action: "Удалить товар из корзины",
expectedResult: "Товар убирается, сумма пересчитывается",
},
// Чекаут
{
key: "checkout.address",
section: "Чекаут",
action: "Выбрать адрес доставки",
expectedResult: "Можно выбрать из сохранённых или добавить новый",
},
{
key: "checkout.delivery",
section: "Чекаут",
action: "Выбрать способ доставки",
expectedResult: "Почта, OZON, Яндекс, 5post — доступны",
},
{
key: "checkout.payment",
section: "Чекаут",
action: "Выбрать способ оплаты",
expectedResult: "Онлайн / при получении — доступны",
},
{
key: "checkout.comment",
section: "Чекаут",
action: "Добавить комментарий к заказу",
expectedResult: "Поле работает, текст сохраняется",
},
{
key: "checkout.create",
section: "Чекаут",
action: "Создать заказ",
expectedResult: "Заказ создаётся, статус DRAFT",
},
// Оплата
{
key: "payment.yookassa",
section: "Оплата",
action: "Оплатить через ЮKassa",
expectedResult: "Редирект на оплату, webhook обрабатывается",
},
{
key: "payment.status",
section: "Оплата",
action: "Проверить статус платежа",
expectedResult: "Статус обновляется после webhook",
},
// Профиль пользователя
{
key: "profile.avatar",
section: "Профиль пользователя",
action: "Управление аватаром",
expectedResult: "Загрузка, отображение, удаление работают",
},
{
key: "profile.settings",
section: "Профиль пользователя",
action: "Изменить настройки профиля",
expectedResult: "Email, имя, способы входа обновляются",
},
{
key: "profile.addresses",
section: "Профиль пользователя",
action: "Управление адресами",
expectedResult: "Добавление, редактирование, удаление, по умолчанию",
},
{
key: "profile.orders",
section: "Профиль пользователя",
action: "Просмотр заказов",
expectedResult: "Список, детали, статусы отображаются",
},
{
key: "profile.messages",
section: "Профиль пользователя",
action: "Сообщения по заказу",
expectedResult: "Отправка, получение, read state работают",
},
{
key: "profile.notifications",
section: "Профиль пользователя",
action: "Настройки уведомлений",
expectedResult: "Вкл/выкл каналов работают",
},
{
key: "profile.delete-account",
section: "Профиль пользователя",
action: "Удалить аккаунт",
expectedResult: "Данные удаляются",
},
// Админ — Товары
{
key: "admin-products.list",
section: "Админ — Товары",
action: "Открыть список товаров",
expectedResult: "Пагинация, поиск работают",
},
{
key: "admin-products.create",
section: "Админ — Товары",
action: "Создать товар",
expectedResult:
"Все поля сохраняются, фото загружаются, публикация работает",
},
{
key: "admin-products.edit",
section: "Админ — Товары",
action: "Редактировать товар",
expectedResult: "Изменения сохраняются",
},
{
key: "admin-products.delete",
section: "Админ — Товары",
action: "Удалить товар",
expectedResult: "Товар удаляется",
},
{
key: "admin-products.images",
section: "Админ — Товары",
action: "Управление изображениями товара",
expectedResult: "Добавление, сортировка, удаление работают",
},
// Админ — Категории
{
key: "admin-categories.crud",
section: "Админ — Категории",
action: "CRUD категорий",
expectedResult: "Создание, редактирование, удаление, сортировка работают",
},
// Админ — Заказы
{
key: "admin-orders.list",
section: "Админ — Заказы",
action: "Открыть список заказов",
expectedResult: "Фильтрация по статусу, внимание отображается",
},
{
key: "admin-orders.details",
section: "Админ — Заказы",
action: "Открыть детали заказа",
expectedResult: "Состав, статус, смена статуса работают",
},
{
key: "admin-orders.messages",
section: "Админ — Заказы",
action: "Ответить на сообщение заказа",
expectedResult: "Сообщение отправляется пользователю",
},
// Админ — Отзывы
{
key: "admin-reviews.list",
section: "Админ — Отзывы",
action: "Открыть список отзывов",
expectedResult: "Фильтрация pending/approved/rejected работает",
},
{
key: "admin-reviews.moderate",
section: "Админ — Отзывы",
action: "Модерировать отзыв",
expectedResult: "Approve/reject работают",
},
// Админ — Пользователи
{
key: "admin-users.list",
section: "Админ — Пользователи",
action: "Открыть список пользователей",
expectedResult: "Email, дата регистрации отображаются",
},
{
key: "admin-users.orders",
section: "Админ — Пользователи",
action: "Просмотр заказов пользователя",
expectedResult: "Заказы пользователя отображаются",
},
// Админ — Галерея
{
key: "admin-gallery.upload",
section: "Админ — Галерея",
action: "Управление галереей",
expectedResult: "Загрузка, удаление, использование в слайдере работают",
},
// Админ — Настройки
{
key: "admin-settings.notifications",
section: "Админ — Настройки",
action: "Настройки уведомлений админа",
expectedResult: "Email, telegram настраиваются",
},
// Инфо-страницы
{
key: "info.pages",
section: "Инфо-страницы",
action: "Открыть инфо-страницы",
expectedResult:
"Доставка, оплата, как заказать, статусы заказов отображаются",
},
{
key: "info.legal",
section: "Инфо-страницы",
action: "Открыть юридические страницы",
expectedResult:
"Политика конфиденциальности, условия использования отображаются",
},
// SSE / Realtime
{
key: "sse.notifications",
section: "SSE / Realtime",
action: "Проверить SSE-уведомления",
expectedResult: "Уведомления приходят в реальном времени",
},
]);
+5
View File
@@ -0,0 +1,5 @@
export declare const ADMIN_UPLOAD_IMAGE_MAX_FILE_BYTES_DEFAULT: 20971520
export declare const ADMIN_UPLOAD_IMAGE_MAX_BYTES: 20971520
export declare function formatAdminImageMaxSizeHint(): string
+9
View File
@@ -0,0 +1,9 @@
const MB = 1024 * 1024
export const ADMIN_UPLOAD_IMAGE_MAX_FILE_BYTES_DEFAULT = 20 * MB
export const ADMIN_UPLOAD_IMAGE_MAX_BYTES = 20 * MB
export function formatAdminImageMaxSizeHint() {
return '20 МБ'
}