В каком месяце прибыль: правило закрытого заказа
Разработка - Механизмы типовых конфигураций
Типовая «Валовая прибыль предприятия» считает по отгрузке. Проверял так: прогонял ее на своей демобазе УТ 11.5.22.149 и сверял с оборотами регистра выручки за тот же период — сходится до копейки. Оговорка, которая дальше станет сюжетом: сверка берет тот же ресурс, что и сам отчет, поэтому она показывает арифметику, а не выбор ресурса. Реализация проведена в марте, значит выручка и себестоимость мартовские, и неважно, что половина этих денег придет в апреле, а часть заказов вообще не оплатят. Внутри мартовской цифры лежит прибыль, которой пока нет.
Разрез, который просил заказчик, формулируется в одну строку: заказ относится к тому месяцу, в котором он закрылся полностью — и отгружен, и оплачен. Отгрузили в марте, деньги пришли в апреле — это апрель. Взяли аванс в феврале, отгрузили в марте — это март. Сделка считается один раз.
Методику не придумывал, а делал по ней отчет на заказ. Код той работы принадлежит заказчику, поэтому здесь нет ни строчки из него: отчет собран заново на своей демобазе УТ 11.5.22.149, платформа 8.3.27.2214, числа ниже из моих прогонов, а состав объектов и опций — из выгрузки конфигурации. Находки про компоновку данных замерены на этой же платформе, про другие версии ничего не скажу.
Файла к статье нет, и это не оговорка: отчет написан и считает, но выкладывать его не стал. Причина разобрана в отдельном тексте про сверку — там она будет понятнее, чем здесь.
Здесь — про правило: как понять, что заказ закрылся, и каким числом его датировать. Это самая дорогая часть работы и самая неочевидная: одна строка определения разворачивается в восемь развилок, и в первой сборке неверно были решены все восемь. Про то, как компоновка данных выводит не то, что ей задали, и про сверку, которая сходилась пять раз и пять раз ничего не доказывала, — отдельные разборы.
Только сначала про грабли. Все мы хоть раз брали по такой задаче готовый запрос из интернета, и вот с ним на новой архитектуре взаиморасчетов ничего не выйдет — по крайней мере, с теми, что попадались мне.
Регистр, на котором стоит готовый рецепт, у меня пуст
В УТ 11.5 — точнее, в релизе 11.5.22.149, что в других, не смотрел — взаиморасчеты живут в двух разных архитектурах, и от того, какая включена у вас, зависит, есть ли вообще данные в регистре.
Старая схема стоит на РегистрНакопления.РасчетыСКлиентамиПоДокументам. Он удобен до неприличия — правда, знаю это по составу метаданных и по прошлым заказным работам, а не по своим данным. У одной записи сразу две даты — регистратор (документ оплаты) и расчетный документ (реализация). Берешь позднюю из двух, проверяешь попадание в период, готово)) С него начинался и разбор, по которому делал ту самую заказную работу.
Посчитал записи в своей базе:
РасчетыСКлиентами = 1 262 РасчетыСКлиентамиПоСрокам = 486 РасчетыСКлиентамиПоДокументам = 0 РасчетыСПоставщикамиПоДокументам = 0
Ноль записей в регистре, на котором держится вся методика. Данных нет, значит и строк в отчете не будет, а человек пойдет искать ошибку у себя.
Пустой регистр объясняю функциональной опцией НоваяАрхитектураВзаиморасчетов, за ней стоит одноименная константа типа Булево. Проверьте у себя за полминуты:
Сообщить(ПолучитьФункциональнуюОпцию("НоваяАрхитектураВзаиморасчетов"));
Когда опция включена, взаиморасчеты пишутся в РегистрНакопления.РасчетыСКлиентами. Моя база пришла уже на новой архитектуре, и старого регистра в ней нет ни одной записью. Опустошает ли переход старый регистр на базе с историей, не проверял: перехода в этой базе не было. А вот то, что при включенной опции старый регистр никто не читает, в конфигурации подкреплено: в общем модуле ВзаиморасчетыСервер обращения к ...ПоДокументам обернуты условием НЕ &НоваяАрхитектураВзаиморасчетов, а ветки под новую архитектуру берут другие таблицы. Кто именно перестал в него писать, по регистраторам не проверял. Включается опция не флажком, а переносом данных — процедура ЗаполнитьРегистрыПриВключенииНовойАрхитектуры в ОперативныеВзаиморасчетыСервер, в интерфейсе это переход на «онлайн взаиморасчеты с партнерами» (общая форма ФормаПереходаНаНовуюАрхитектуруВзаиморасчетов). Обратный ход тоже предусмотрен, рядом лежит ОчиститьРегистрыПриВыключенииНовойАрхитектуры.
Мелочь, по которой переход заметен без всякого кода: в конфигурации есть зеркальная опция НеНоваяАрхитектураВзаиморасчетов, и к ней привязаны девять отчетов («Расчеты с клиентами», «Дебиторская задолженность», «Платежная дисциплина клиентов» и другие) плюс три общих команды. Раз они привязаны к опции, после перехода из интерфейса они должны уйти. Своими глазами этого не видел по той же причине: переходить было не с чего. Чем эти отчеты заменили, осталось за рамками работы.
Так вот, в новой архитектуре двух дат в одной записи больше нет. Есть регистр остатков РасчетыСКлиентами с измерением ОбъектРасчетов и двумя полезными нам реквизитами: ДатаРегистратора и ОперацияВзаиморасчетов. Семантику замерил прогоном, а не вывел из названий: «Оплата» пишется расходом, «Отгрузка» приходом, остаток равен долгу клиента, а отрицательный остаток — это аванс. По оплате и отгрузке Сумма положительна, знак направления несет вид движения. Но это не общее правило регистра: операция «Перенос взаиморасчетов» пишет Сумма и отрицательной, и увидел это позже, дважды, оба раза замером.
Отсюда и вся конструкция: обе даты приходится собирать самому из движений одного регистра.
Что тут считается закрытой сделкой
Дата закрытия — максимум из двух: дата полной отгрузки и дата, на которую оплата дошла до порога. Фильтр по периоду накладывается только на нее.
Звучит как задача на полдня, и первая версия так и писалась полдня. Дальше пять кругов правок разобрали по косточкам оба слова, «отгружен» и «оплачен». Ни одно не решается тем простым сравнением, которое приходит в голову первым. Ниже разбор каждого.
Оплата: порог вместо равенства
Условие «оплачено полностью» написано порогом с параметром, а не как «процент равен 100»:
И ВТНакопление.Накоплено >= ВТБазаОплаты.База * &ПорогОплаты / 100
По умолчанию стоит 99. Причина прозаическая: копеечные хвосты. В живых базах попадались после округления НДС и банковских комиссий; на демоданных источник хвоста не воспроизводил, хвост завел руками. Заплатить за это придется вот чем: 99 процентов от миллиона — это недоплата в 10 000 рублей, и такой заказ отчет посчитает закрытым. На крупных суммах порог стоит поднимать ближе к сотне.
В тестовых данных специально завел заказ на 43 000, оплаченный на 42 995,70, то есть на 99,99%. Для собственника сделка закрыта, для строгого равенства — нет. Прогон по марту это и показывает: при пороге 99 пять строк, при пороге 99,9999 и при 100 — четыре, копеечный заказ выпадает. Выручка без НДС при этом падает с 214 999,99 до 179 166,66.
Сверхоплата
Порог — нижняя граница, и это важнее, чем кажется. Клиент занес аванс больше суммы заказа или заплатил дважды — процент уходит за сотню, и при условии «= 100» такие сделки выпали бы, а терять их обиднее всего. В данных такой заказ есть, оплачен на 105%, и в отчете он стоит с признаком «Сверхоплата».
Что вообще считать оплатой
Тут думал, что вопрос арифметический, а он оказался бухгалтерским))
В перечислении ОперацииВзаиморасчетов шесть значений: Оплата, Отгрузка, КорректировкаНакладной, ВозвратДенежныхСредств, ПереносВзаиморасчетов, КорректировкаЗадолженности. Первая версия честно брала только «Оплату», и заказ, закрытый взаимозачетом, навсегда оставался недоплаченным. Обобщение просится само: оплатой считать любое движение, которое уменьшает долг клиента.
Оно закрывает одну дыру и открывает другую. Долг уменьшает и взаимозачет, и списание безнадежной задолженности, а смысл у них противоположный. В первом случае деньги пришли, просто не по этому заказу. Во втором денег не будет никогда, и это не закрытая сделка, а убыток.
Оба случая проверены на живых документах, потому что рассуждением такое не закрывается.
Взаимозачет — оплата. Заказ на 43 000 отгружен 10 ноября и деньгами не оплачен; по второму заказу того же партнера висит аванс 43 000; «Взаимозачет задолженности» от 20 ноября переносит аванс. В регистре это легло двумя движениями операции «Перенос взаиморасчетов»: расход +43 000 на объекте расчетов первого заказа и −43 000 на втором. Отчет закрывает заказ 20 ноября со стопроцентной оплатой, как и должен.
Списание долга — не оплата. Отдельный заказ на ту же сумму: отгрузка 10 ноября, деньгами 21 500 пятнадцатого ноября, «Корректировка задолженности» на остаток 21 500 двадцатого. Рабочая сборка держит заказ в незакрытых с процентом 50,00 и отдельной колонкой «Погашено неденежно». Сборка, где эта правка откачена, показывает его закрытым 20 ноября со стопроцентной оплатой. То есть половина суммы получена деньгами, половина списана, а отчет докладывает собственнику, что сделка закрыта целиком.
Первая попытка завести такой сценарий провалилась. Копировал образец «Корректировки задолженности» из демобазы, а у него объект расчетов — претензия, и реквизит шапки Претензия оставался заполненным. Документ проводился, а движения в РасчетыСКлиентами не было вовсе, и выглядело это как будто правка работает. Заработало с ТипРасчетов = РасчетыСКлиентом, объектом расчетов заказа и очищенной претензией. Проведенный документ без движений — отличный способ обмануть себя на полчаса))
Граница периода: два числа вместо одного
Самая коварная правка из всех, и я сделал ее дважды неправильно, в обе стороны.
Сначала «Оплачено» обрезалось концом периода, а база оплаты нет: в марте заказ показывал 100%, в годовом отчете 111,63%. Оговорюсь сразу, потому что это ровно та ловушка, про которую вся статья: сами по себе эти два числа ничего не доказывают. Годовые 111,63% — законная сверхоплата, разобранная выше, и точно так же они выглядели бы у исправного отчета. Что числитель и знаменатель считаются на разных границах, видно из кода, а не из процентов. Логичный ход напрашивался сам: снять границу и там, и там.
Снял целиком. И получил кое-что похуже перепутанных процентов: прошлое начало меняться.
Заказ отгружен 11 марта на 129 000, оплачен 10 апреля. Март, сформированный до апрельской оплаты, показывал его в незакрытых с причиной «на конец периода оплаты нет». Март, сформированный после, не показывал нигде: в закрытые он не попадал, потому что дата закрытия десятое апреля вне марта, а в незакрытые — потому что признак «закрыт» уже истина. Заказ просто исчезал из отчета. Причем вариант «Незакрытые заказы и причина» обещает ровно этот случай (отгрузили, денег нет) и именно его перестал показывать.
Второй след той же правки: заказ на 43 000, оплата 43 000 двадцатого марта и доплата 5 000 десятого апреля. Март печатал «Оплачено 48 000,00» и «% оплаты 111,63». На тридцать первое марта по заказу было получено ровно 43 000.
Вывод, за который заплатил двумя кругами: одного числа тут не хватает, нужны два. Оплата на конец периода отвечает на вопрос «что было видно тогда», оплата по итогу сделки — на вопрос «чем дело кончилось», и признак закрытия обязан опираться на первое, иначе отчет за прошлый месяц перестает быть воспроизводимым. Отчет, который меняет прошлое, нельзя сдавать никому: сверять его не с чем.
Почему дата оплаты — первая, а не последняя
Соблазн взять дату последнего платежа по заказу. Ломается она на доплате. Заказ оплачен 20 марта полностью, а 10 апреля клиент довносит еще 5 000, скажем, пересчитали доставку. По «последнему платежу» закрытая мартовская сделка уехала бы в апрель. Поэтому дата оплаты — это момент, когда накопленный итог впервые дошел до порога.
И ровно на этом месте нашлась дыра в обратную сторону. Заказ закрыт 12 июня на 100%, а 20 июля клиент вернул 21 500 — и он продолжал стоять в закрытых с процентом 50,00. Дата первого достижения порога никуда не делась, а денег уже не было. В моем сценарии возврат лег в регистр не операцией «Возврат денежных средств», как ожидал, а «Переносом взаиморасчетов» расходом на −21 500. Других способов оформить возврат не пробовал, так что за общее правило это не выдаю.
Лечится тем, что порог обязан держаться и по итогу, а не только в какой-то момент. Условие добавлено в оба места: и в признак закрытия, и в расчет даты.
Отгрузка: деньги против количества
Первая версия считала полноту отгрузки по деньгам — по ресурсу Сумма регистра РаспоряженияНаОтгрузку. Выглядит естественно: заказали на 86 000, отгрузили на 86 000, остаток ноль.
Мы привыкли, что остаток по регистру распоряжений — это деньги, и работает такой счет до первой отгрузки по цене, отличной от цены заказа.
Крайний случай попался в сценарии, который завел под другое: заказ на две штуки, пятнадцатого августа одна штука по 86 000, шестнадцатого оплата, десятого сентября вторая штука по цене ноль. В РасчетыСКлиентами второй отгрузки не оказалось вообще — там по этому заказу всего две записи, замерил специально. Денег она не двигает, значит для денежного признака ее не существует.
Из того же механизма следует мягкий вариант, куда более частый. Заказ, отгруженный целиком со скидкой, по деньгам выглядит недоотгруженным. А заказ, где отгрузили одну штуку по цене двух, выглядит отгруженным полностью. Отдельного сценария со скидкой не гонял, так что это вывод из устройства регистра, а не прогон. Но крайний случай прогнан, и он из того же корня.
Правка: и полнота, и дата отгрузки считаются по количеству и по одному регистру РаспоряженияНаОтгрузку. Полнота — как остаток по ресурсу «Заказано» на конец периода, дата — как последнее расходное движение по тому же ресурсу.
До правки отчет считал заказ отгруженным целиком пятнадцатого августа, потому что денежный остаток обнулился, и закрывал его датой оплаты, шестнадцатым. После правки в августе он стоит в незакрытых с остатком одна единица, а в сентябре закрыт десятого числа с выручкой 71 666,67.
Мораль, которую повесил бы над столом: полноту считайте тем же ресурсом, в котором эта полнота задана. Вывел это на регистре распоряжений и на одном прогоне; звучит как общее правило, но проверено на одном месте. Количество заказано — количеством и мерьте.
Правило целиком: максимум из двух дат
Дата закрытия — максимум из даты полной отгрузки по количеству и даты, на которую оплата дошла до порога и на нем удержалась.
Одна строка, а за ней восемь развилок, и каждая из них в первой сборке была решена неверно: равенство вместо порога, одна граница периода вместо двух, последняя дата оплаты вместо первой, сумма вместо количества. Ни одна из этих ошибок не роняет отчет. Все восемь дают числа, и числа правдоподобные.
Что из этого стоит забрать
Полноту считайте тем же ресурсом, в котором она задана. Отгрузка задана количеством — мерьте количеством, а не суммой. На этом я потерял больше всего времени, и это единственное место, где у меня есть ровно один прогон вместо нескольких.
Порог, а не равенство. Копеечные расхождения в оплате есть всегда, и сделка от них не перестает быть закрытой.
Период — два числа. Заказ, закрывшийся внутри марта, и заказ, закрывшийся в марте по данным за год, — это один и тот же заказ, и отчет не должен отвечать про него по-разному.
Дата оплаты — первая, на которой порог взят и удержан, а не последняя из платежей.
Все это — про расчет. Но между верным расчетом и тем, что покупатель увидит на экране, стоит еще компоновка данных, и она умеет выводить не то, что ей задали. Про это — отдельный разбор.
А у вас порог оплаты в таких разрезах задан в процентах или в сумме?
Вступайте в нашу телеграмм-группу Инфостарт
Вы можете заказать платную адаптацию этой статьи под ваши задачи на «Бирже заказов».
- 0% комиссии — оплата напрямую исполнителю;
- Исполнители любого масштаба — от отдельных специалистов до команд под проект;
- Прямой обмен контактами между заказчиком и исполнителем;
- Безопасная сделка — при необходимости;
- Рейтинги, кейсы и прозрачная система откликов.