wasrusgen1-crm/docs/SCHEMA.md

41 KiB
Raw Permalink Blame History

SCHEMA.md — Контракт данных CRM мебельного салона

Проект: CRM для сети мебельных салонов (РФ) Версия: 1.1 · 2026-05-29 Реализация: JavaScript, in-memory в data.js (в перспективе — localStorage). Бэкенда в первом прототипе нет. Соглашения: поля — camelCase (английские); id — строки вида 'ord_001', 'cli_001', 'usr_001'.

Этот документ — единственный источник истины по структуре данных. Если экран показывает поле, которого здесь нет, — прав документ, а не экран. Если разработчику что-то непонятно — это баг документа, заведите вопрос, не додумывайте.


0. Что этот контракт чинит (из аудита)

Проблема аудита Решение в схеме
Заказы и клиенты не существуют в общем источнике Сущности orders, clients в data.js
Клиент — строка, не сущность clients[] с id, заказы ссылаются clientId
KPI захардкожены KPI вынесены в раздел 5 «Вычисляемые поля», не хранятся
Связи по именам Все связи через id (раздел 2)
3 формата времени Единый стандарт: ISO 8601 (раздел 6, БП-1)
Enum как свободные строки Все enum зафиксированы в _DICT (раздел 3)
Комиссии и конфиги в вёрстке Сущность config + commissionRules (раздел 1.7)
id менеджера по инициалам ('ak') id = 'usr_001' (стабильный), инициалы → поле initials
Оценки сотрудников нигде не хранятся / непрозрачны Сущность ratings (раздел 1.8), рейтинг вычисляется (раздел 5), анонимность для оцениваемого (БП-14)
Ставка комиссии захардкожена одним числом Трёхуровневый приоритет: orders.commissionRateusers.commissionRateconfig.commissionRules (БП-5)

1. Сущности (хранимые)

Порядок объявления в data.js: configsalonsusersclientsordersappointmentsrequestsshiftRequestsratings. Сущность не может ссылаться на ещё не объявленную — отсюда порядок.

1.1 salons — салоны сети

Поле Тип Обяз. Пример Комментарий
id string да 'sal_lenina' Первичный ключ. Префикс sal_. Неизменяемый.
name string да 'Салон на Ленина' Отображаемое название.
address string да 'пр. Ленина, 42' Физический адрес.
color string да '#3B82F6' HEX-цвет для UI (графики, бейджи салона).
adminId string|null да 'usr_002' FK → users.id. Директор салона. null если вакансия.
revenuePlan number да 1700000 План выручки на текущий месяц, ₽. Целое.
ordersPlan number да 30 План по кол-ву заказов на месяц. Целое.
active boolean да true false — салон закрыт/заморожен, скрыт из активных списков.

Не хранить здесь: orders, revenue, overdue, newLeads — это факт, вычисляется из orders (раздел 5).

1.2 users — сотрудники (все роли)

Поле Тип Обяз. Пример Комментарий
id string да 'usr_001' Первичный ключ. Префикс usr_ + порядковый номер. Никогда не основан на инициалах (коллизии). Неизменяемый.
name string да 'Анна Кузнецова' Полное имя.
initials string да 'АК' Инициалы для аватара/компактного UI. Только для отображения, не ключ.
role string да 'manager' Enum → _DICT.role. См. 3.1.
salonId string|null да 'sal_lenina' FK → salons.id. У КД — null (над салонами).
color string да '#7C3AED' HEX-цвет сотрудника для шахматки/графиков.
phone string нет '+79110000000' Контакт.
active boolean да true false — уволен/в отпуске; скрыт из распределения заказов, история сохраняется.
commissionRate number|null нет 0.05 Индивидуальная ставка комиссии (доля, 0.05 = 5%). null → берётся из config.commissionRules по роли.

Не хранить здесь: visits, deals, revenue, conversion, avg, rating, kpi — всё это KPI, вычисляется (раздел 5). Хранение = тот самый «захардкоженный KPI» из аудита.

1.3 clients — клиенты (сущность, не строка)

Поле Тип Обяз. Пример Комментарий
id string да 'cli_001' Первичный ключ. Префикс cli_. Неизменяемый.
name string да 'Орлова Мария' ФИО клиента.
phone string да '+79219998877' Основной контакт. Уникален в пределах сети (БП-6).
email string|null нет 'orlova@mail.ru' Необязателен.
source string да 'instagram' Откуда пришёл. Enum → _DICT.clientSource. См. 3.4.
salonId string да 'sal_lenina' FK → salons.id. Салон, к которому привязан клиент.
managerId string|null да 'usr_001' FK → users.id. Ответственный менеджер. null — не распределён.
status string да 'lead' Enum → _DICT.clientStatus. См. 3.5.
createdAt string (ISO) да '2026-05-20T09:14:00+03:00' Дата появления в CRM. ISO 8601.
note string|null нет 'Интересует кухня Hettich' Свободный комментарий.

Не хранить здесь: число заказов, сумму покупок, LTV — вычисляется из orders по clientId (раздел 5).

1.4 orders — заказы (новая сущность, центральная)

Поле Тип Обяз. Пример Комментарий
id string да 'ord_001' Первичный ключ. Префикс ord_. Неизменяемый.
clientId string да 'cli_001' FK → clients.id. Обязательна (БП-2).
managerId string да 'usr_001' FK → users.id. Менеджер, оформивший заказ.
salonId string да 'sal_lenina' FK → salons.id. Денормализация ради быстрых выборок по салону; должна совпадать с clients.salonId.
status string да 'measuring' Enum → _DICT.orderStatus. См. 3.2.
amount number да 60500 Сумма заказа, ₽. Целое.
prepayment number да 20000 Внесённая предоплата, ₽. 0 если нет.
commissionRate number|null нет 0.10 Переопределение ставки комиссии для этого заказа. null → используется ставка менеджера (users.commissionRate) или дефолт из config (БП-5).
createdAt string (ISO) да '2026-05-20T10:30:00+03:00' Когда оформлен. ISO 8601.
dueDate string (ISO date)|null да '2026-06-15' Плановая дата готовности/выдачи. null — не назначена. Формат YYYY-MM-DD.
closedAt string (ISO)|null да null Когда закрыт (done/canceled). null пока открыт.
paymentStatus string да 'partial' Enum → _DICT.paymentStatus. См. 3.3.

Не хранить: «просрочен ли заказ», «сумма риска» — вычисляется из dueDate + status + текущей даты (раздел 5, isOverdue).

1.5 appointments — записи в расписании (шахматка)

Заменяет _CHESS_DATA. Вместо матрицы менеджер × строка-времени — плоский список событий с нормальным временем.

Поле Тип Обяз. Пример Комментарий
id string да 'apt_001' Первичный ключ. Префикс apt_.
managerId string да 'usr_001' FK → users.id. Кто проводит.
clientId string|null да 'cli_001' FK → clients.id. null — слот занят без клиента (бронь/перерыв).
salonId string да 'sal_lenina' FK → salons.id.
type string да 'consult' Enum → _DICT.appointmentType. См. 3.6.
status string да 'busy' Enum → _DICT.appointmentStatus. См. 3.7.
startAt string (ISO) да '2026-05-29T14:00:00+03:00' Начало. ISO 8601 с таймзоной.
endAt string (ISO) да '2026-05-29T16:00:00+03:00' Конец. Длительность = endAt startAt (БП-3).
orderId string|null нет 'ord_001' FK → orders.id, если визит привязан к заказу.

Слоты «свободно» в UI не хранятся — это пустоты между appointments на рабочей сетке (раздел 5, freeSlots).

1.6 requests — заявки менеджеров администратору / КД

Заменяет _MGR_REQUESTS.

Поле Тип Обяз. Пример Комментарий
id string да 'req_001' Первичный ключ. Префикс req_.
authorId string да 'usr_001' FK → users.id. Кто создал.
salonId string да 'sal_lenina' FK → salons.id.
type string да 'supply' Enum → _DICT.requestType. См. 3.8.
priority string да 'high' Enum → _DICT.priority. См. 3.9.
title string да 'Закончились образцы ткани' Короткий заголовок.
body string да 'Нет образцов искусственной замши...' Текст заявки.
status string да 'new' Enum → _DICT.requestStatus. См. 3.10.
createdAt string (ISO) да '2026-05-29T09:14:00+03:00' ISO 8601. Заменяет 'сегодня 09:14'.
assigneeId string|null нет 'usr_002' FK → users.id. Кому эскалировано (null — на администратора салона по умолчанию).

1.7 config — конфигурация и комиссии (singleton)

Заменяет конфиги и комиссии, зашитые в вёрстку. Один объект на всю систему.

Поле Тип Обяз. Пример Комментарий
kpiNorm number да 80 Норма KPI (порог), %. Ниже — флаг. Было захардкожено в мокапе.
currency string да 'RUB' Валюта системы.
timezone string да 'Europe/Moscow' Базовая таймзона для расчётов дат.
workdayStart string да '10:00' Начало рабочего дня (для сетки шахматки).
workdayEnd string да '20:00' Конец рабочего дня.
slotMinutes number да 60 Шаг сетки расписания, минут.
commissionRules array да см. ниже Дефолтные ставки комиссии по ролям. Ставка по умолчанию — 7% (0.07).

commissionRules[] — элемент:

Поле Тип Пример Комментарий
role string 'manager' Enum → _DICT.role.
rate number 0.07 Доля от amount закрытого заказа (0.07 = 7%).

Приоритет ставки (от высшего к низшему): orders.commissionRate (ставка конкретного заказа) → users.commissionRate (индивидуальная ставка менеджера) → config.commissionRules по role (дефолт, 7% для менеджера). Комиссия начисляется только с заказов в статусе done (БП-5).

1.8 ratings — оценки этапов заказа и сотрудников

Хранит оценки, которые участники выставляют друг другу и заказу. Рейтинг сотрудника не хранится — вычисляется (раздел 5).

Поле Тип Обяз. Пример Комментарий
id string да 'rat_001' Первичный ключ. Префикс rat_.
orderId string да 'ord_001' FK → orders.id.
stage string да 'assembly' Этап заказа, для которого оценка. Enum → _DICT.ratingStage. См. 3.13.
authorId string да 'usr_001' FK → users.id. Кто оставил оценку. Скрыт от оцениваемого (БП-14).
targetId string да 'usr_003' FK → users.id. Кого оценивают.
scores object да {quality:8, speed:7, cleanliness:9} Набор критериев → оценка 110 (целое). Ключи зависят от stage. См. 3.14.
comment string|null нет 'Аккуратная работа' Свободный комментарий.
createdAt string (ISO) да '2026-06-01T15:00:00+03:00' Когда оставлена. ISO 8601.

Кто кого оценивает:

  • Клиент оценивает весь заказ (обязательно при закрытии — БП-13). stage='order'.
  • Менеджер оценивает замерщика (stage='measuring') и сборщика (stage='assembly').
  • Замерщик оценивает менеджера.
  • Сборщик оценивает менеджера.
  • Администратор и КД видят все оценки, включая authorId (кто оценил).
  • Оцениваемый (targetId) видит только итоговые оценки — не видит, кто конкретно его оценил (БП-14).

Шкала: 110 (целое). Среднее арифметическое по критериям scores = итоговая оценка за этап. Рейтинг сотрудника = среднее по всем его этапам за период (раздел 5).


2. Связи (явные)

Все связи — по id, не по имени. N→1 читается «много слева на одного справа».

salons.adminId          → users.id        (1→1, директор салона)
users.salonId           → salons.id        (N→1, сотрудник в салоне; null у КД)
clients.salonId         → salons.id        (N→1)
clients.managerId       → users.id         (N→1, ответственный менеджер; null = не распределён)
orders.clientId         → clients.id       (N→1, у заказа всегда есть клиент)
orders.managerId        → users.id         (N→1)
orders.salonId          → salons.id        (N→1, денормализация = clients.salonId)
appointments.managerId  → users.id         (N→1)
appointments.clientId   → clients.id       (N→1; null допустим)
appointments.salonId    → salons.id        (N→1)
appointments.orderId    → orders.id        (N→1; nullable)
requests.authorId       → users.id         (N→1)
requests.salonId        → salons.id        (N→1)
requests.assigneeId     → users.id         (N→1; nullable)
shiftRequests.authorId  → users.id         (N→1)
shiftRequests.withUserId→ users.id         (N→1; null для отгула)
ratings.orderId         → orders.id        (N→1)
ratings.authorId        → users.id         (N→1, кто оценил; скрыт от targetId)
ratings.targetId        → users.id         (N→1, кого оценивают)
commissionRules.role    → _DICT.role       (значение из справочника)

Диаграмма верхнего уровня:

            ┌──────────┐
            │  salons  │◄──────────────┐
            └────┬─────┘               │ salonId (денормализация)
        adminId  │  salonId            │
                 ▼                     │
            ┌──────────┐   managerId   │
   ┌───────►│  users   │◄──────────────┤
   │        └────┬─────┘               │
   │ authorId    │ managerId           │
   │             ▼                     │
   │        ┌──────────┐  clientId  ┌──┴──────┐
   │        │ clients  │◄───────────┤ orders  │
   │        └──────────┘            └─────────┘
   │             ▲                       ▲
   │             │ clientId              │ orderId
   │        ┌────┴──────────┐            │
   │        │ appointments  ├────────────┘
   │        └───────────────┘
   │
┌──┴──────────┐   ┌───────────────┐   ┌──────────┐
│  requests   │   │ shiftRequests │   │ ratings  │
└─────────────┘   └───────────────┘   └──────────┘

3. Справочники (_DICT) — все enum зафиксированы

Хранятся в data.js как var _DICT = { ... }. В полях сущностей допустимы только ключи из этих справочников. Свободные строки запрещены (правка аудита). UI берёт label/color отсюда, а не хардкодит.

3.1 role — роль сотрудника

Ключ Русское название Описание
owner КД (собственник) Видит всю сеть. salonId = null.
admin Администратор Директор одного салона.
manager Менеджер Продавец-консультант.
measurer Замерщик Проводит замеры, привязан к салону.
assembler Сборщик Выполняет монтаж, привязан к салону.

Роли measurer и assembler активны в v1 — реализованы в мокапах mockup_measurer.html и mockup_assembler.html.

3.2 orderStatus — статус заказа

Ключ Русское название Описание
lead Лид Новый контакт, не конвертирован в заказ.
measuring Замер Назначен/проводится замер.
project Проект Разрабатывается дизайн-проект.
tech Техника Подбор и согласование техники.
technolog Технолог Проверка технологом.
production Производство Передан на фабрику.
assembly Сборка Монтаж у клиента.
done Закрыт Выдан, оплачен. Начисляется комиссия.
canceled Отменён Сделка не состоялась.

3.3 paymentStatus — статус оплаты

Ключ Русское название Описание
none Без оплаты Предоплаты нет.
partial Частичная Внесена предоплата, остаток не закрыт.
paid Оплачен Оплачено полностью.
refunded Возврат Средства возвращены клиенту.

3.4 clientSource — источник клиента

Ключ Русское название Описание
walkin Зашёл в салон Офлайн-трафик.
instagram Instagram Соцсеть.
avito Avito Доска объявлений.
referral Рекомендация По совету клиента.
site Сайт Заявка с сайта.
other Другое Прочее.

3.5 clientStatus — статус клиента в воронке

Ключ Русское название Описание
lead Лид Новый контакт, без заказа.
active Активный Есть открытый заказ.
repeat Повторный Был ≥1 закрытый заказ.
lost Потерян Отказ/неактивен.

3.6 appointmentType — тип записи в расписании

Ключ Русское название Описание
consult Консультация Первичная встреча/подбор.
follow Повторный контакт Дозвон/повторная встреча.
measure Замер Выезд/замер.
tech Техническая Согласование проекта, документы.

3.7 appointmentStatus — статус записи

Ключ Русское название Описание
busy Занято Слот забронирован, событие предстоит.
done Завершено Встреча прошла.
noshow Не пришёл Клиент не явился.
canceled Отменено Запись отменена.

Статуса free нет: свободное время — это отсутствие записи, а не запись со статусом (см. раздел 5, freeSlots).

3.8 requestType — тип заявки

Ключ Русское название Описание
supply Снабжение Образцы, каталоги, материалы.
escalate Эскалация Конфликт/проблема, нужно вмешательство.
visit Выезд/транспорт Нужен ресурс для выезда к клиенту.
schedule Расписание Вопрос по сменам/графику.
other Другое Прочее.

3.9 priority — приоритет

Ключ Русское название Описание
low Низкий Можно отложить.
normal Обычный Штатный.
high Высокий Срочно, влияет на деньги/клиента.

3.10 requestStatus — статус заявки

Ключ Русское название Описание
new Новая Не обработана.
inWork В работе Взята в обработку.
done Выполнена Закрыта.
rejected Отклонена Отказано.

3.11 shiftRequestType — тип запроса по смене

Ключ Русское название Описание
swap Обмен сменами С другим сотрудником (withUserId обязателен).
off Отгул Без замены (withUserId = null).
extra Доп. смена Просьба добавить смену.

3.12 shiftRequestStatus — статус запроса по смене

Ключ Русское название Описание
pending На рассмотрении Ждёт решения администратора.
approved Одобрено Согласовано.
declined Отклонено Отказано.

3.13 ratingStage — этап заказа, для которого выставлена оценка

Ключ Название
measuring Замер
project Проект
assembly Сборка
order Весь заказ (клиентская оценка)

3.14 ratingCriteria — критерии оценки (ключи scores)

Ключ Название Этапы
quality Качество работы measuring, project, assembly
speed Скорость measuring, assembly
cleanliness Чистота assembly
deadlines Сроки project, assembly
communication Взаимодействие measuring
overall Общая оценка order
service Сервис order
result Результат order

Набор критериев в ratings.scores зависит от stage: Замер → quality, speed, communication; Проект → quality, deadlines; Сборка → quality, speed, cleanliness, deadlines; Весь заказ → overall, service, result.


4. Сущность shiftRequests — запросы по сменам

Заменяет _SHIFT_REQS. Вынесена отдельно от справочников для полноты.

Поле Тип Обяз. Пример Комментарий
id string да 'shr_001' Префикс shr_.
authorId string да 'usr_002' FK → users.id. Кто просит.
salonId string да 'sal_lenina' FK → salons.id.
type string да 'swap' Enum → _DICT.shiftRequestType. См. 3.11.
withUserId string|null да 'usr_003' FK → users.id. С кем обмен; null для off.
dateFrom string (ISO date) да '2026-05-31' Дата запроса. YYYY-MM-DD. Заменяет '31 мая ↔ 1 июня'.
dateTo string (ISO date)|null нет '2026-06-01' Вторая дата для обмена; null если одна.
status string да 'pending' Enum → _DICT.shiftRequestStatus. См. 3.12.

5. Вычисляемые поля (НЕ хранятся)

Считаются на лету из хранимых данных. Запрещено сохранять как поля сущностей — это и есть «захардкоженный KPI» из аудита. Реализуются хелперами в data.js.

По менеджеру (users с role='manager'), за период

Поле Формула (словами) Источник
visits Кол-во appointments менеджера со status='done' за период appointments
deals Кол-во orders менеджера со status='done' за период orders
revenue Сумма amount закрытых заказов менеджера за период orders
conversion deals / visits × 100, % (0 если visits=0) производное
avg revenue / deals, средний чек (0 если deals=0) производное
commission По БП-5: ставка × сумма закрытых заказов orders + users + config
kpi Сводный балл; базово = conversion, сравнивается с config.kpiNorm производное
belowNorm kpi < config.kpiNorm → флаг производное

По сотруднику — рейтинг из оценок

Поле Формула (словами) Источник
employeeRating(userId, period) Среднее всех значений scores по всем ratings где targetId=userId за период ratings
orderRating(orderId) Клиентская оценка заказа: из ratings с orderId, stage='order' и authorId = clientId заказа ratings

Рейтинг сотрудника = среднее всех scores по всем его этапам за период (БП-15). Не хранится.

По салону (salons)

Поле Формула Источник
orders Кол-во заказов салона за месяц orders по salonId
revenue Сумма amount закрытых заказов салона за месяц orders
overdue Кол-во заказов с isOverdue=true orders
overdueRisk Сумма amount просроченных заказов orders
newLeads Кол-во clients со status='lead' за период clients
planFulfillment revenue / revenuePlan × 100, % производное
salonStatus 'ok'/'warn'/'risk' по planFulfillment и overdue производное

По заказу (orders)

Поле Формула Источник
isOverdue dueDate < сегодня И status ∉ {done, canceled} orders + дата
balance amount prepayment, остаток к оплате производное

По клиенту (clients)

Поле Формула Источник
ordersCount Кол-во заказов клиента orders по clientId
ltv Сумма amount закрытых заказов клиента orders

По расписанию

Поле Формула Источник
freeSlots Слоты рабочей сетки (config.workdayStart..End шагом slotMinutes) без appointments производное

6. Бизнес-правила (утверждения, не код)

  • БП-1. Единый формат времени. Все моменты времени хранятся как ISO 8601 с таймзоной ('2026-05-29T14:00:00+03:00'); только-дата — как YYYY-MM-DD. Человекочитаемые строки ('сегодня 09:14', '31 мая') и сетки-слоты как ключи запрещены. Форматирование для UI — на стороне отображения.
  • БП-2. Заказ без клиента не существует. orders.clientId обязателен и должен указывать на существующего clients.id. Создать заказ можно только после создания клиента.
  • БП-3. Длительность записи = разница времён. appointments.endAt > startAt. Длительность не хранится отдельным полем.
  • БП-4. Согласованность салона. orders.salonId должен совпадать с clients.salonId ответственного клиента. При смене салона клиента — пересобрать денормализацию.
  • БП-5. Комиссия только с закрытых заказов. Начисляется лишь для orderStatus='done'. Ставка определяется в порядке приоритета: orders.commissionRateusers.commissionRateconfig.commissionRules[role].rate (дефолт 7%). База — orders.amount. Результат — getCommission(userId, period) в разделе 5.
  • БП-6. Телефон клиента уникален в сети. Дубликат телефона = тот же клиент. Повторное обращение не создаёт нового clients, а привязывается к существующему.
  • БП-7. KPI не хранится. Любой показатель эффективности (раздел 5) вычисляется. Поле KPI в хранимой сущности — ошибка ревью.
  • БП-8. Enum только из _DICT. Запись значения вне справочника недопустима. Новое значение → сначала в _DICT, потом в данные.
  • БП-9. id неизменяем и осмыслен по префиксу. После создания id не меняется (на него ссылаются). Префиксы: sal_, usr_, cli_, ord_, apt_, req_, shr_, rat_.
  • БП-10. Удаление — мягкое. Сотрудники/салоны/клиенты не удаляются физически (на них висит история заказов) — ставится active=false / status='lost'. Жёсткое удаление запрещено.
  • БП-11. Эскалация = смена адресата. requests по умолчанию идёт администратору салона. КД видит её, только если assigneeId указывает на пользователя с role='owner' (эскалация) или type='escalate'.
  • БП-12. КД вне салона. У role='owner' salonId = null. Любая выборка «по салону» для КД означает «по всем салонам».
  • БП-13. Клиентская оценка обязательна при закрытии. Перевод заказа в статус done невозможен без клиентской оценки (ratings с stage='order'). Без неё закрыть заказ нельзя.
  • БП-14. Анонимность оценки для оцениваемого. Оцениваемый (targetId) не видит authorId. Только пользователи с ролью admin и owner видят, кто оценил.
  • БП-15. Рейтинг сотрудника вычисляется. Рейтинг = среднее всех scores по всем ratings где targetId=userId за период. Не хранится — вычисляется (раздел 5, employeeRating).

7. Доступ по ролям

Принцип: менеджер видит своё, администратор — свой салон, КД — всю сеть. Замерщик и сборщик — только свои назначенные этапы.

Сущность / данные Менеджер (manager) Замерщик (measurer) Сборщик (assembler) Администратор (admin) КД (owner)
Свои клиенты чтение/запись все клиенты салона все клиенты сети
Чужие клиенты в своём салоне
Свои заказы чтение/запись только назначенные (этап замера) только назначенные (этап сборки) все заказы салона все заказы сети
Чужие заказы свой салон
Финансы заказа (amount, комиссия) свои свой салон
Своё расписание (appointments) чтение/запись своё своё весь салон (шахматка) все салоны
Свои KPI только просмотр только просмотр только просмотр KPI всех в салоне KPI всей сети
Чужие KPI свой салон
Оставлять оценки (ratings) замерщика и сборщика менеджера менеджера просмотр всех (видит authorId) просмотр всех (видит authorId)
Получать оценки от замерщика/сборщика от менеджера и клиента от менеджера и клиента
Видеть authorId оценки (только итог) (только итог) (только итог)
Создание requests создаёт создаёт создаёт обрабатывает свой салон видит эскалации
shiftRequests создаёт свои создаёт свои создаёт свои одобряет/отклоняет в салоне просмотр
config / commissionRules просмотр своих ставок полное управление
Управление users сотрудники своего салона все, включая админов
Управление salons свой салон (чтение) полное
Сводка по сети (все салоны) только свой салон

Замерщик и сборщик видят только свои назначенные заказы (конкретный этап) и своё расписание; финансовые данные заказа (сумма, комиссия) им недоступны. Оценки оставляют и получают по правилам раздела 1.8 и БП-1315.

Разграничение в первом прототипе — на уровне фильтрации в UI по currentUser.role и currentUser.salonId. Серверной проверки нет (бэкенда нет) — это известное ограничение прототипа, не передавать в прод без авторизации на сервере.


8. Скелет data.js (порядок и форма)

var _DICT = { role:{...}, orderStatus:{...}, ratingStage:{...}, ratingCriteria:{...}, /* ... разделы 3.13.14 */ };

var config = {
  kpiNorm:80, currency:'RUB', timezone:'Europe/Moscow',
  workdayStart:'10:00', workdayEnd:'20:00', slotMinutes:60,
  commissionRules:[ {role:'manager', rate:0.07} ]   // дефолт 7%
};

var salons        = [ /* раздел 1.1 */ ];
var users         = [ /* раздел 1.2 */ ];
var clients       = [ /* раздел 1.3 */ ];
var orders        = [ /* раздел 1.4 — включая commissionRate */ ];
var appointments  = [ /* раздел 1.5 */ ];
var requests      = [ /* раздел 1.6 */ ];
var shiftRequests = [ /* раздел 4 */ ];
var ratings       = [ /* раздел 1.8 */ ];

// Вычисляемые (раздел 5) — функции, НЕ поля:
function getMgrStats(userId, period){ /* visits/deals/revenue/conversion/avg/kpi */ }
function getSalonStats(salonId, period){ /* orders/revenue/overdue/... */ }
function isOverdue(order){ /* dueDate + status + сегодня */ }
function getFreeSlots(userId, date){ /* сетка минус appointments */ }
function getCommission(userId, period){ /* БП-5: orders.commissionRate → users.commissionRate → config */ }
function employeeRating(userId, period){ /* среднее scores по ratings.targetId=userId */ }
function orderRating(orderId){ /* клиентская оценка: ratings stage='order', authorId=clientId */ }

Конец контракта. При расхождении кода и этого документа — правится код. При расхождении документа и реальности — заводится правка документа, не молчаливый обход.