240 lines
25 KiB
Markdown
240 lines
25 KiB
Markdown
# Аналитика компании
|
||
|
||
Аналитика встроена во вкладку «Аналитика компании» на странице `/finances`.
|
||
Внутренние вкладки: проекты, продажи, сотрудники, производство и журнал фактов.
|
||
Выбранные вкладки запоминаются для пользователя; обычный и широкий режимы
|
||
используют то же переключение, что и управление зарплатой. Фильтры открываются модальным окном по кнопке в шапке финансов;
|
||
под внутренними вкладками остаётся короткая строка применённых фильтров.
|
||
Отмена закрывает окно без изменения отчёта. Карточки адаптируются к ширине
|
||
рабочей области. В обычном режиме и при ширине рабочей области меньше 928px
|
||
списки по умолчанию свёрнуты до заголовка со счётчиком и стрелкой.
|
||
После открытия списка табличные строки представлены карточками с раскрывающимися
|
||
подробностями. В широком режиме списки всегда открыты и показывают таблицы.
|
||
Это относится к сводным отчётам, документам выплат и журналу фактов.
|
||
В широком режиме при ширине рабочей области от 928px основной блок сочетает
|
||
график и таблицу в двух колонках, если таблица помещается без горизонтальной
|
||
прокрутки. Иначе график располагается сверху, а таблица — снизу на всю ширину.
|
||
Компоновка пересчитывается при изменении размера области и данных таблицы.
|
||
Дополнительные блоки располагаются попарно.
|
||
Таблицы имеют собственную прокрутку и закреплённые заголовки.
|
||
В обычном и узком режиме графики открываются только модальным окном по кнопке
|
||
в заголовке списка, в том числе когда список свёрнут.
|
||
|
||
У каждого списка есть график с выбором показателя: разные единицы не смешиваются.
|
||
Месячные данные представлены линией с разрывами на неизвестных значениях;
|
||
остальные — сравнением до семи позиций. Высота области всех графиков — 280px.
|
||
Для явно отмеченных суммируемых показателей при числе позиций больше семи
|
||
показываются шесть крупнейших по модулю и «Прочее» — сумма остальных с учётом
|
||
знака. Число объединённых позиций видно в подписи и подсказке. Неизвестные
|
||
значения исключаются и отмечаются отдельно. Проценты, средние, возраст и стаж
|
||
не суммируются; для них показываются семь крупнейших значений.
|
||
Месячные ряды сохраняют все месяцы. Полные списки и CSV не сокращаются.
|
||
Графики сводных списков учитывают поиск по всем страницам результата.
|
||
Для документов и журнала используется только текущая загруженная страница,
|
||
что явно подписано над графиком; аннулированные записи журнала исключены.
|
||
Поиск, страницы, CSV и права на действия сохраняются в обоих режимах. Методика и инструкции открываются по вопросику в шапке,
|
||
пояснения к отдельным таблицам — по вопросику рядом с названием. Предупреждения
|
||
о качестве данных остаются в отчётах. Старый адрес `/finances/analytics`
|
||
перенаправляет на `/finances?tab=analytics`, открывая встроенную вкладку.
|
||
Используются визуальные компоненты и темы портала. Изображения из локальных
|
||
материалов служат примерами; их показатели, числа и оформление не являются ТЗ.
|
||
|
||
## Возможности
|
||
|
||
- Продажи по товарам, каналам, сочетанию товара и канала, клиентам и месяцам.
|
||
- Выручка, себестоимость, привязанные расходы, прибыль по учтённым расходам и маржа.
|
||
- Зарплата каждого сотрудника, изменение зарплаты, дни работы, отпуска и больничного,
|
||
уплаченные налоги, возраст, стаж и динамика численности команды.
|
||
- Выпуск, продажи произведённых товаров, производственные закупки и графики
|
||
с переключением между закупками, выручкой и прибылью. Прибыль по товарам и каналам повторно доступна
|
||
в производственном отчёте и рассчитывается тем же сервисом.
|
||
- Журнал подтверждённых фактов: выпуск, производственные закупки, уплаченные налоги
|
||
сотрудника, общие расходы, поступления и выплаты денег.
|
||
- Локальные проекты объединяют продажи, расходы и платежи связанных внешних
|
||
объектов; подтверждённые часы получают предварительную оценку до закрытия
|
||
зарплатного периода и точную оценку по его итоговому начислению после закрытия.
|
||
- Выбор дат включительно, валюты и источника продаж. Поиск, страницы таблиц и CSV
|
||
всех строк текущей группировки с учётом поиска. Диапазон ограничен 367 днями.
|
||
|
||
## Источники и методика
|
||
|
||
Продажи и возвраты берутся из `commerce.Sale` и `SaleLine`, расходы — из `Cost`.
|
||
Операции с `counts_as_sale=False` и связанные с ними расходы исключаются.
|
||
Возвраты уменьшают количество, выручку и себестоимость. Расходы учитываются по
|
||
собственной дате; их знак сохраняется, в том числе для компенсаций.
|
||
|
||
Выручка равна сумме после скидок и возвратов в денежной базе источника. Сервис не
|
||
пересчитывает НДС. Прибыль по учтённым расходам равна выручке минус себестоимость
|
||
минус привязанные расходы. Общие расходы и налоги не распределяются автоматически.
|
||
Этот показатель не следует принимать за окончательную чистую прибыль компании.
|
||
Правила распределения общехозяйственных расходов и налогов требуют согласования.
|
||
|
||
Себестоимость с `metadata.cost_source="unavailable"` считается неизвестной.
|
||
Нулевая себестоимость без подтверждения `manual` или `known_zero` также считается
|
||
неизвестной. Для затронутых групп прибыль и маржа возвращаются `null`, в UI — «—».
|
||
API дополнительно возвращает арифметический результат по известным расходам
|
||
`contribution_profit`, но интерфейс не показывает его как подтверждённую прибыль.
|
||
|
||
Клиенты имеют внешний ID и имя на продаже. Миграция восстанавливает их из
|
||
сохранённого `SourceRecord.payload.agent`, если информация присутствует. Новая
|
||
синхронизация МойСклад запрашивает контрагента вместе с документом. ID клиента
|
||
дополняется ID источника, поэтому одинаковые внешние ID разных систем не
|
||
объединяются. Когда имеется только имя, используется имя внутри источника;
|
||
для надёжного сопоставления необходимо заполнить внешний ID.
|
||
Расходы без привязки к продаже остаются в группе неизвестного клиента.
|
||
|
||
Валюты не суммируются и автоматически не конвертируются. Продажи разных систем
|
||
не дедуплицируются по предполагаемым совпадениям: при дублирующем импорте одного
|
||
оборота следует выбрать один источник.
|
||
|
||
Зарплата берётся из опубликованного расчёта каждого периода. Новая черновая
|
||
ревизия не заменяет опубликованную. Учитываются только периоды, целиком входящие
|
||
в выбранные даты. Начисления нескольких периодов одного месяца складываются по
|
||
месяцу окончания расчётного периода. Средняя зарплата сотрудника — начисленная
|
||
сумма до удержаний, делённая на число месяцев с расчётами. Среднее компании — по
|
||
парам «сотрудник / месяц» с расчётами. Неопубликованные и отсутствующие расчёты
|
||
не заменяются нулями. Рост сравнивает первый и последний месяц с опубликованными расчётами внутри
|
||
диапазона. Границы сравнения едины для всех сотрудников и показаны в интерфейсе.
|
||
Сотрудник включается в сравнение только при наличии обоих расчётов и ненулевой
|
||
начальной зарплате.
|
||
|
||
Рабочие дни — записи успешного анализа посещаемости с положительными часами и
|
||
без признака отсутствия. Отпуска и больничные — календарные дни по кадровым
|
||
событиям с существующим приоритетом пересекающихся событий. Стаж учитывает
|
||
увольнения и повторный приём, а не дату создания учётной записи. При отсутствии
|
||
кадровой истории текущая численность использует активность учётной записи;
|
||
историческая динамика включает только известную кадровую историю. Неизвестные
|
||
даты рождения и найма исключаются из средних и учитываются в счётчиках пропусков.
|
||
|
||
Налоги берутся только из фактически оплаченных записей журнала. Удержания
|
||
расчётного листка не означают уплаты налога. Производственный отчёт включает
|
||
продажи товаров с неаннулированным фактом выпуска не позже конца периода.
|
||
Количество выпуска вводится в единицах соответствующего товара и не зависит от выбранной валюты. Закупка может
|
||
относиться к конкретному конечному товару либо оставаться общей. Производственные
|
||
закупки не вычитаются повторно из прибыли поверх себестоимости продаж.
|
||
|
||
Фильтр проекта использует локальную карточку и применяется к продажам через её
|
||
внешние привязки, а к ручному журналу и трудозатратам — напрямую. Фильтр источника
|
||
влияет на продажи и связанные расходы; журнал остаётся общим.
|
||
Денежный поток считается только по поступлениям и выплатам из журнала. Нулевое
|
||
число записей не подтверждает отсутствие фактических выплат, налогов или выпуска.
|
||
|
||
## Доступ и журнал
|
||
|
||
Все endpoints требуют авторизации и существующего доступа к управлению финансами,
|
||
включая allow-list `FinanceSettings.management_users`. Ответы имеют `private,
|
||
no-store`; фронтовое скрытие ссылки не подменяет серверную проверку.
|
||
|
||
Для добавления фактов нужно `finance.add_reportingentry`, для аннулирования —
|
||
`finance.change_reportingentry`. Действует существующий временный административный
|
||
доступ `SIMPLE_ADMIN_ACCESS`. Права на просмотр финансов сами по себе не дают права
|
||
изменять журнал.
|
||
|
||
Номер документа уникален внутри вида записи. Записи не редактируются и не удаляются:
|
||
исправление выполняется аннулированием и новой записью с новым номером. Автор и дата
|
||
создания, автор и дата аннулирования сохраняются. Повторное аннулирование идемпотентно.
|
||
Будущие факты, отрицательные суммы и несовместимые с видом записи поля отклоняются.
|
||
|
||
## API
|
||
|
||
- `GET /api/v1/finance/analytics/report/?section=sales|people|production|projects`
|
||
- `GET /api/v1/finance/analytics/options/?search=...`
|
||
- `GET|POST /api/v1/finance/analytics/entries/`
|
||
- `POST /api/v1/finance/analytics/entries/{id}/void/`
|
||
|
||
Параметры отчёта и списка фактов: `date_from`, `date_to`, `currency`, локальный
|
||
`project` либо `unassigned`; параметр `source` применяется к продажам и внешним
|
||
привязкам. Список фактов использует стандартную пагинацию API. `options`
|
||
возвращает локальные проекты и первые 100 товаров и сотрудников по строке поиска.
|
||
CSV экранирует кавычки, переносы строк и потенциальные формулы в текстовых полях.
|
||
|
||
## Подключение и проверка
|
||
|
||
Перед запуском обновлённого backend выполните `python manage.py migrate`:
|
||
миграции Commerce 0012–0014, Projects 0001 и Finance 0032–0034 добавляют клиента
|
||
продажи, внешний справочник, локальные проекты и журнал фактов.
|
||
Для старых документов без имени клиента может потребоваться повторная синхронизация.
|
||
|
||
```bash
|
||
cd backend
|
||
pytest tests/finance/test_analytics.py tests/finance/test_projects.py tests/commerce -q
|
||
python manage.py makemigrations --check --dry-run
|
||
python manage.py check
|
||
```
|
||
|
||
```bash
|
||
cd frontend
|
||
node --import tsx --test src/lib/analytics.test.js
|
||
npx tsc --noEmit
|
||
```
|
||
|
||
Исходная книга учёта оплат автоматически не импортируется: в ней есть внешние
|
||
ссылки с ошибками и лист с учётными данными. Импорт этой книги требует отдельного
|
||
сопоставления колонок, подтверждения исходных сумм и подготовки безопасной копии.
|
||
Фраза «всё из годового финансового отчёта» требует отдельного перечня показателей
|
||
или самого годового отчёта: книга оплат не задаёт полный состав такого отчёта.
|
||
Индекс здоровья, планы и прогнозы из примерных изображений не включены в объём
|
||
реализации текстового ТЗ.
|
||
|
||
## Локальные проекты и внешние источники
|
||
|
||
Раздел «Проекты» использует локальную карточку портала как основную строку. В неё
|
||
по точным внешним привязкам собираются оплаченные расходы, документы платежей,
|
||
продажи и прямые расходы всех подключённых сервисов. Ручные выплаты, закупки и
|
||
общие расходы журнала ссылаются на локальную карточку напрямую. Подтверждённые
|
||
распределения сотрудников добавляют трудозатраты в часах, число дней учёта и
|
||
участников. Пока расчётный период открыт, стоимость часа предварительная: система
|
||
использует текущий расчёт зарплаты, а до его появления — действующую утверждённую
|
||
базовую ставку, разделённую на норму часов периода. После закрытия периода
|
||
стоимость становится точной: итоговое начисление из опубликованного расчётного
|
||
листка, включая премии, доплаты и корректировки, распределяется по фактически
|
||
отработанным часам. Полученная стоимость часа умножается только на подтверждённые
|
||
часы проекта. Черновые ставки не используются. Закрытый период без итогового
|
||
листка, а также часы без периода, нормы или фактических часов сохраняются как
|
||
неоценённые и не превращаются в ноль.
|
||
|
||
Фильтр локального проекта работает в продажах, производстве, проектах и журнале.
|
||
Вариант «Без привязки» объединяет внешние документы, для которых ещё не выбрана
|
||
локальная карточка, а также документы без проекта источника. В списке платежей
|
||
сохраняются и локальное имя, и исходное имя проекта для проверки происхождения.
|
||
|
||
Синхронизация получает `/entity/project` (включая архив), `/entity/paymentout`
|
||
и `/entity/cashout` с проектом, статьёй расходов, получателем и валютой. Продажи
|
||
получают связь `project` из отгрузки или возврата. Для старых продаж связь
|
||
восстанавливается из сохранённого исходного документа, если поле присутствует.
|
||
Проекты идентифицируются сочетанием источника и внешнего ID, а не названием.
|
||
|
||
«Оплачено по источникам» — сумма проведённых исходящих платежей и расходных ордеров
|
||
за период в выбранной валюте. Проект определяется по явному полю документа;
|
||
из связанных заказов и приёмок он не угадывается. Непроведённые документы и
|
||
документы, исчезнувшие после полностью успешного обхода источника, исключаются.
|
||
Повторный импорт обновляет ту же запись. Последние полученные исходные документы сохраняются; отсутствующие платежи
|
||
помечаются флагом и не удаляются. Импортированные платежи доступны только для чтения.
|
||
При ошибке обхода оставшиеся документы не помечаются отсутствующими.
|
||
|
||
Платёж может быть переводом между счетами, авансом, погашением долга, закупкой или возвратом: его нельзя
|
||
автоматически вычитать из прибыли поверх себестоимости. Поэтому оплаченные
|
||
суммы, ручные выплаты, закупки и общие расходы показаны отдельными колонками.
|
||
Ручные записи не следует создавать повторно для уже импортированных платежей.
|
||
При фильтре источника ручные факты относятся к нему через выбранный проект;
|
||
ручные факты без проекта не включаются в конкретный источник.
|
||
|
||
Дополнительный API: `GET /api/v1/finance/analytics/project-payments/` с обычной
|
||
пагинацией. `section=projects` возвращает сводку, строки проектов, статьи и месяцы.
|
||
Параметр `project` — локальный ID проекта либо `unassigned`. Поле `project`
|
||
доступно в ручной записи, продаже и прямом расходе. Справочник проектов возвращается
|
||
в `analytics/options/`. Права и `no-store` совпадают с остальной аналитикой.
|
||
|
||
Миграции: Commerce 0014, Projects 0001 и Finance 0033–0034. Миграция 0034
|
||
сохраняет прежнюю внешнюю связь ручной записи для аудита и заполняет локальный
|
||
проект там, где уже существовала точная привязка. Обычная синхронизация включает
|
||
проекты и исходящие платежи. Для отдельной загрузки без товаров и изображений:
|
||
|
||
```bash
|
||
python manage.py sync_moysklad_projects <UUID-подключения>
|
||
```
|
||
|
||
Контракты API: [проекты](https://github.com/moysklad/remap-1.2-java-sdk/blob/master/docs/Project.md),
|
||
[исходящие платежи](https://github.com/moysklad/remap-1.2-java-sdk/blob/master/docs/PaymentOut.md),
|
||
[расходные ордера](https://github.com/moysklad/remap-1.2-java-sdk/blob/master/docs/CashOut.md).
|