Рентабельность объекта в ERPNext: из чего складывается Gross Margin и что в него не попадёт
Рентабельность объекта в ERPNext показывает карточка Project: поле Gross Margin равно выставленным заказчику счетам минус три вида затрат — труд по табелям, закупки по проведённым счетам и материалы, списанные на объект. Ниже: что куда попадает, что не попадает и как проверить цифру.
Из чего ERPNext складывает себестоимость объекта и где её смотреть
Начну с места, где цифра живёт. Рентабельность считается в карточке проекта (Project), в блоке Margin. Там два поля только для чтения: «Gross Margin» в деньгах и «Gross Margin %». Они не вводятся руками: система пересчитывает их из пяти связанных полей, и каждое из них питается своим документом. Мы используем эту механику в ERPNext под ключ для прибыльности объекта: объект там — проект, этап — задача, а затраты собираются из закупок, склада и табелей. Дальше разбираю её по исходному коду версии 15, потому что глава в нашей базе знаний давала формулу с повторяющимися слагаемыми, и копировать её дословно нельзя.
По коду Project (v15) формула такая: Gross Margin = Total Billed Amount минус сумма трёх затрат: Total Costing Amount, Total Purchase Cost и Total Consumed Material Cost. Процентная маржа равна Gross Margin, делённой на Total Billed Amount, умноженной на сто. Обратите внимание на то, что стоит в начале: не выручка по заказу и не сумма договора, а именно то, что выставлено счетами покупателю (Sales Invoice) и проведено. Пока вы не выставили счёт, Gross Margin объекта отрицательна на сумму затрат, а процент в коде v15 при нулевых счетах просто равен нулю. Для строителя с поэтапным выставлением это ожидаемо, но директору надо объяснить заранее, иначе он решит, что система врёт.
Откуда берётся каждая сумма, в одной таблице: | Поле проекта | Откуда считается | Что обязательно | |---|---|---| | Total Billed Amount (via Sales Invoice) | проведённые Sales Invoice | поле Project в счёте или в строке счёта | | Total Purchase Cost (via Purchase Invoice) | проведённые Purchase Invoice | поле Project в строке счёта поставщика | | Total Consumed Material Cost (via Stock Entry) | проведённые Stock Entry | Project в шапке, строка без склада-получателя | | Total Costing Amount (via Timesheet) | проведённые Timesheet | Project в строке табеля, ставка себестоимости | | Total Sales Amount (via Sales Order) | проведённые Sales Order | справочное поле, в маржу не входит |
Что видно из таблицы. Во-первых, ни одна цифра не появляется сама: каждая требует привязки документа к проекту. Во-вторых, все суммы берутся только из проведённых документов (docstatus = 1): черновики не считаются. В-третьих, заявка (Material Request), заказ поставщику (Purchase Order) и приёмка (Purchase Receipt) в себестоимость проекта не входят, поэтому отчёт отстаёт от реальных обязательств. Это не ошибка, а свойство схемы, и о нём надо помнить, когда смотрите на цифры утром до того, как бухгалтер провёл вчерашние счета.
Закупки в проекте: почему Total Purchase Cost растёт только после счёта поставщика
Total Purchase Cost суммирует чистую сумму строк (Net Amount в валюте компании) из проведённых счетов поставщика, где в строке стоит ваш проект. Механика видна в коде счёта: при проведении (submit) счёт прибавляет сумму строки к закупочной стоимости проекта и пересчитывает маржу, при отмене вычитает. То есть цифра обновляется сразу по событию, без ночных задач. Если же по какой-то причине сумма в карточке не сходится, в форме проекта есть кнопка «Update Costing and Billing», которая пересчитывает все поля заново по базе.
Отсюда практическое правило, которое я прописываю в регламенте снабжения. Проект указывают в строках счёта поставщика (Purchase Invoice), а не только в заказе. На заказе проект тоже полезен, потому что он тянется в связанные документы, но в себестоимость попадёт только счёт. Если снабженец проводит счёт без проекта, расход уйдёт в общий пул и в рентабельности объекта не появится. Это самая частая причина фразы «у нас в проекте нулевые закупки, а материал уже на объекте».
Доставка. Если счёт транспортной компании приходит позже приёмки материала, её распределяют на себестоимость уже принятых позиций документом Landed Cost Voucher: по количеству или по сумме. Но у него есть ограничение, о котором забывают: документ распределяет стоимость, но сам не создаёт кредиторскую задолженность перед перевозчиком. Долг надо закрыть счётом или платёжным документом, иначе в отчёте о прибылях и убытках появится минус по статье расходов. Ограничения этого документа и разметку центров затрат я разбираю отдельно, а здесь важно только одно: стоимость доставки доходит до объекта через цену материала на складе, а не через Total Purchase Cost.
Материалы со склада: как списание на проект попадает в маржу
Если материал идёт через склад, затрата попадает в проект другим путём. Правильная цепочка движения такая. Сначала материал перемещают с центрального склада на склад объекта документом Stock Entry с назначением Material Transfer: остаток на объекте растёт, в расход ничего не списывается. Потом фактический расход списывают документом Stock Entry с назначением Material Issue со склада объекта и указывают счёт расходов. В этом документе в шапке заполняется поле Project. Только после этого сумма строки попадёт в Total Consumed Material Cost.
Точное условие в коде: берутся проведённые Stock Entry с этим проектом, а в суммирование идут строки, у которых не заполнен склад-получатель (Target Warehouse). Именно поэтому перемещение на склад объекта в расход не попадает, а списание попадает: у списания склада-получателя нет. Это удобно. Но пока Material Issue не оформлен, стоимость материала, лежащего на объекте, в рентабельности не видна. На практике я прошу начальников участков списывать материал по факту работ раз в неделю, а не в конце месяца, иначе маржа объекта в середине месяца выглядит слишком оптимистично.
Теперь предупреждение, которое я нашёл при разборе кода и которое надо проверить на вашем стенде. Закупочная цифра и цифра списания считаются независимо друг от друга. Если вы купили материал на склад, провели счёт поставщика с проектом в строке, а потом списали тот же материал на тот же проект, то затраты могут быть посчитаны дважды: один раз в закупках, второй раз в списании. Чтобы этого не случилось, я принимаю простое правило: материал, купленный на объект «с колёс», идёт через счёт поставщика с проектом; материал, купленный на склад и списываемый на объекты, идёт через списание с проектом, а в строках счёта проект не ставим. Одно правило на материал, а не оба.
Труд: Timesheet, Activity Cost и ставка себестоимости часа
Труд бригады попадает в проект через табель (Timesheet). В строке табеля указывают проект, задачу (Task) и вид работ (Activity Type), а себестоимость часа берётся из ставки. Приоритет такой: сначала ищется запись Activity Cost для пары «сотрудник и вид работ», если её нет, берутся значения по умолчанию из вида работ. В строке табеля видны Costing Rate и Costing Amount (ставка и сумма себестоимости) и Billing Rate с Billing Amount (ставка и сумма для выставления). В себестоимость проекта идёт Costing Amount, и только из проведённых табелей.
Отсюда два риска. Первый: ставка себестоимости должна быть настоящей, а не нулевой. Если для сотрудника и вида работ нет Activity Cost, а в виде работ нет ставки, часы в системе есть, а себестоимость труда равна нулю, и рентабельность выглядит лучше, чем есть. Я всегда прохожу по справочнику вместе с бухгалтером: ставка должна включать зарплату, начисления и накладные, которые вы готовы отнести на час работы. Второй риск: табели, которые не проводят. Если бригадир их заполняет, но не подтверждает, система видит ноль. Поэтому проведение табеля входит в еженедельную рутину.
Простой пример на условных числах. Бригада из трёх человек отработала на объекте неделю: 3 × 40 часов = 120 часов. Ставка себестоимости для вида работ «Монтаж потолков» задана 650 ₽ за час. Costing Amount = 120 × 650 = 78 000 ₽. Эта сумма в разрезе проекта и задачи прибавится к Total Costing Amount после проведения табеля. Если бригада отработала на двух объектах, часы разносят по разным строкам с разными проектами, а не записывают в одну.
Что в расчёт не попадает и почему отчёт может «врать»
Отчёты не врут, они считают ровно то, что им передали. Для сравнения: как устроен контур закупок и склада в других открытых системах, видно в материале про ERPNext для производства и про Odoo и российскую локализацию. Но вывод читателя расходится с реальностью, если он не знает границ. Ниже границы, которые я проговариваю на запуске. Часть из них следует из кода v15, часть из глав базы знаний; там, где я не смог подтвердить, прямо пишу «проверьте».
Отдельно про расхождение с главой базы знаний, которое я нашёл при проверке. В главе сказано, что в рентабельность входят авансовые отчёты (Expense Claim). В коде Project версии 15 нет ни поля с суммой авансовых отчётов, ни такого слагаемого в формуле маржи. Если вам нужно учитывать авансовые расходы по объекту, закладывайте это как отдельную настройку и проверяйте на стенде в вашей версии и с вашим приложением HRMS: коробочный Gross Margin их не включает. Это важнее, чем кажется: строительные компании часто закрывают мелкие закупки через подотчёт. Про то, как автоматизировать сверку выписок и категоризацию расходов, у меня есть отдельный разбор.
И ещё одно уточнение про названия. Отчёта под названием «Project vs Budget Cost», упомянутого в главе, я в списке отчётов ядра v15 не нашёл. План-факт по бюджетам даёт Budget Variance Report: в нём можно выбрать измерение, в том числе Project. Для сводок по проектам есть Project Summary, Project Billing Summary и Project wise Stock Tracking. Бюджет (Budget) контролирует деньги по счетам расходов, а не количество материала, и об этом я подробно пишу в других материалах.
- Черновики и непроведённые документы: не считаются нигде.
- Заявки, заказы поставщикам и приёмки без счёта: в себестоимость проекта не входят, цифра отстаёт.
- Выручка до выставления счёта: пока нет проведённого Sales Invoice с проектом, маржа отрицательна.
- Total Sales Amount по заказам: справочная сумма, в формулу маржи не входит.
- Авансовые отчёты (Expense Claim): в коробочной формуле v15 слагаемого нет, проверьте в своей версии.
- Затраты без привязки к проекту: в рентабельность не попадут, сколько бы их ни было.
- Смета по ФЕР и ТЕР: в ERPNext её нет, итоговую ресурсную спецификацию загружают из внешних программ.
Разбор: три объекта «Потолка и Стены» и одна пропущенная привязка
Условный пример. Ремонтная компания «Потолок и Стена», 9 рабочих мест: директор, сметчик, два прораба, снабженец, бухгалтер и три монтажника в штате. Три объекта в работе, затраты ведутся в ERPNext, регламентированный учёт в 1С. Директор открыл карточки проектов в конце месяца и увидел валовую маржу. Цифры условные, посчитаны по формуле из раздела 1.
Результат по трём объектам, в рублях: | Объект | Счета заказчику | Закупки по счетам | Списано на объект | Труд по табелям | Gross Margin | % | |---|---|---|---|---|---|---| | Квартира 54 м² | 1 000 000 | 520 000 | 145 000 | 180 000 | 155 000 | 15,5 | | Офис 120 м² | 1 800 000 | 900 000 | 210 000 | 320 000 | 370 000 | 20,6 | | Магазин 80 м² (как увидел директор) | 1 200 000 | 350 000 | 150 000 | 250 000 | 450 000 | 37,5 | Магазин выглядел лучшим объектом, и это насторожило сметчика: по смете ожидали около четверти. Он поднял журнал закупок и нашёл счёт поставщика на 170 000 ₽, проведённый снабженцем без проекта в строках: в день проведения снабженец был на объекте с телефона и выбрал склад, но не проект. После привязки счёта к проекту Total Purchase Cost вырос с 350 000 до 520 000 ₽, затраты составили 920 000 ₽ (520 000 + 150 000 + 250 000), Gross Margin упала до 280 000 ₽, то есть до 23,3 %.
Что мы изменили после этого. Поле Project в строках счёта поставщика сделали обязательным для закупок по объектам, а общие закупки без объекта оформляет отдельная роль. В регламент добавили ежедневную сверку: счета без проекта фильтруются списком и разбираются снабженцем до конца дня. Еженедельно бухгалтер проверяет, что табели проведены и что на Material Issue стоит проект. Ни одна из этих мер не требует доработки кода, это дисциплина и обязательные поля. Но без неё рентабельность превращается в красивую цифру, которой нельзя доверять.
Как проверить цифру на стенде до того, как показывать её директору
Перед тем как показывать рентабельность руководителю, я делаю на стенде контрольный прогон по одному тестовому проекту. Он занимает час и экономит месяцы недоверия. Создаю проект, привязываю к нему по одному документу каждого типа и после каждого шага смотрю поля проекта. Порядок: счёт поставщика с проектом в строке, Material Issue с проектом в шапке, табель с ставкой, счёт заказчику. После каждого проведения сверяю поле проекта с суммой документа.
Для быстрой сверки без интерфейса удобна консоль bench. Команды ниже читают значения полей проекта; название проекта и сайта подставьте свои:
bench --site site1.example.com consolefrappe.db.get_value(
"Project", "PROJ-0001",
["total_billed_amount", "total_purchase_cost",
"total_consumed_material_cost", "total_costing_amount",
"gross_margin", "per_gross_margin"],
as_dict=True,
)Формулу проверьте сами: gross_margin должен равняться total_billed_amount минус три затраты. Расхождение значит, что у вас версия с другой логикой или документ без привязки: тогда нажмите «Update Costing and Billing» в форме проекта и повторите.
Что я проверяю обязательно: 1) черновик счёта поставщика не меняет Total Purchase Cost, проведённый меняет; 2) Material Transfer на склад объекта не меняет Total Consumed Material Cost, Material Issue меняет; 3) непроведённый табель не даёт Costing Amount; 4) нет двойного счёта одного материала; 5) цифра по Sales Order не влияет на маржу. Если хотя бы один пункт не сходится с тем, что я описал, значит, у вас другая версия или другая настройка, и важнее выяснить это на стенде, а не на отчёте директору. Показать контрольный прогон на живых данных можно на демостенде erp-demo.itfresh.ru по запросу. Развернуть такой стенд на вашем сервере можно в рамках услуги внедрения open source.
Частые вопросы
Как посмотреть прибыль по строительному объекту в ERPNext?
Откройте карточку Project и найдите блок Margin: поля Gross Margin и Gross Margin %. Они считаются как Total Billed Amount минус затраты по табелям, закупкам и списанию материалов. Цифры верны, только если все счета, списания и табели привязаны к проекту и проведены. План-факт по бюджетам даёт Budget Variance Report.
Почему в проекте нулевые затраты по закупкам, хотя заказы оформлены?
Total Purchase Cost считается только из проведённых счетов поставщика (Purchase Invoice, docstatus = 1) с заполненным полем Project в строке. Заявка, заказ и приёмка в эту сумму не входят. Проверьте, что счёт проведён, а не сохранён черновиком, и что проект указан в строке, а не только в заказе.
Попадает ли доставка материалов в себестоимость объекта?
Через Landed Cost Voucher расходы на доставку и разгрузку распределяются на материалы по количеству или по сумме и входят в цену материала на складе. Но документ сам не создаёт долг перед перевозчиком: нужен счёт или платёжный документ, иначе в отчёте о прибылях и убытках появится минус.
Как ERPNext считает стоимость часа работы бригады?
В Timesheet фиксируются часы по задаче и виду работ. Ставка себестоимости берётся из Activity Cost для пары «сотрудник и вид работ», при её отсутствии из вида работ. Costing Amount из проведённого табеля идёт в Total Costing Amount проекта. Нулевая ставка даёт нулевую себестоимость труда.
Можно ли в ERPNext считать рентабельность по российской смете?
Смету по ФЕР и ТЕР ERPNext не ведёт: её считают во внешних программах, а в систему загружают итоговую ресурсную спецификацию. Рентабельность в ERPNext сравнивает факт затрат с выставленными счетами; бюджет можно задать через Budget с привязкой к проекту, проверьте поведение в своей версии.
Источники
- Исходный код Project, ERPNext v15 (GitHub) — Проверены update_costing, calculate_gross_margin, update_purchase_costing, update_billed_amount; формула Gross Margin; кнопка «Update Costing and Billing». https://github.com/frappe/erpnext/blob/version-15/erpnext/projects/doctype/project/project.py
- Исходный код Purchase Invoice и Stock Entry, ERPNext v15 — Проверено: при проведении счёта поставщика обновляется Total Purchase Cost проекта; Total Consumed Material Cost суммируется по проведённым Stock Entry с проектом и строками без Target Warehouse. https://github.com/frappe/erpnext/blob/version-15/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
- Документация ERPNext: Project Costing — Проверено: Activity Type, Activity Cost, Timesheet и расчёт стоимости как ставка на часы. https://docs.frappe.io/erpnext/user/manual/en/project-costing
- Список отчётов ядра ERPNext v15 (GitHub) — Проверено: Budget Variance Report, Project Summary, Project Billing Summary, Project wise Stock Tracking; отчёта «Project vs Budget Cost» в ядре v15 не найдено. https://github.com/frappe/erpnext/tree/version-15/erpnext/projects/report
- Глава 03 и 05 базы знаний ITfresh — Разделы «Себестоимость проекта», «Списание на проект», Landed Cost Voucher; расхождения с кодом (Expense Claim, название отчёта) отмечены в тексте. projects/erpnext-kb-20261001/kb/03_proekty_stroitelstvo.md
