Как могли бы выглядеть регистры в 1С при наличии ООП

image

В 1С одним из ключевых элементов системы являются регистры. Этот термин имеет свой аналог в английском языке — ledger. Он первоначально появился в бухгалтерской практике, но со временем его логика начала использоваться и в других сферах.

В отличие от 1С, где регистры являются одним из встроенных типов, в самой платформе lsFusion такого понятие нету. Зато в ней есть наследование, полиморфизм и агрегации, что, в частности, позволяет реализовать аналогичную логику регистров. В этой статье на примерах я покажу как именно.
Регистр — это набор записей, каждая из которых отражает некоторое изменение состояния для некоторого множества субъектов (или измерений).

В 1С различают 4 вида регистров:

  1. Регистр бухгалтерии
  2. Регистр расчета
  3. Регистр накоплений
  4. Регистр сведений


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

Регистры накоплений


Любую запись в регистре можно рассматривать как объект некоторого абстрактного класса. Предположим нужно реализовать простой регистр, который рассчитывает остаток по товару на складе.

Для этого объявим абстрактный класс SkuLedger:


Формально, один экземпляр которого будет отражать единичное изменение остатка по заданному товару и заданному складу на определенное количество (положительное или отрицательное).

Зададим у него измерения как абстрактные свойства типов Sku (товар) и Stock (склад) соответственно. Их нужно будет реализовать при наследовании конкретных классов от класса регистра:


Добавим свойство, которое будет обозначать время изменения остатка:
И, наконец, добавим то, что называется в 1С ресурсом. А именно количество, на которое изменяется остаток данной записью регистра.
Теперь посчитаем текущий остаток на основе записей в регистре:
По умолчанию, в базе данных не будет храниться текущий остаток. При обращении к нему будет формироваться запрос, который рассчитает его исходя из текущего состояния регистра. Чтобы оно хранилось постоянно и автоматически обновлялось при изменении регистра, нужно в конце его объявления добавить ключевое слово MATERIALIZED. Наличием этого флага, по сути, и определяется будет это регистр накопления остатков или оборотов в терминологии 1С.

Существует возможность на построенный таким образом остаток добавить ограничение на то, что он должен быть всегда положительным:


Платформа будет автоматически контролировать то, что при записи в регистр, остаток останется больше нуля. Если условие будет нарушено, то изменения не запишутся в базу данных, а пользователю будет выдано соответствующее сообщение. Кстати, желающие могут сравнить, как это ограничение реализовано в 1С УТ, чтобы оценить истинную боль, испытываемую 1С программистами.

При необходимости быстро считать оборот по товару, например, за год можно построить следующее свойство:


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

Аналогичным образом можно построить свойство, которое будет определять остаток на определенное время:


Для расчета этого значения будет сделан запрос, в соответствии с которым регистр будет прочитан полностью от начала до заданной даты. Но на практике обычно обращение идет к наиболее поздним датам. Поэтому, если текущий остаток является хранимым (MATERIALIZED), то логично рассчитывать это свойство в обратную сторону от текущего остатка:
Естественно, это будет эффективно выполняться, только если по свойству dateTime построен индекс:
Весь код по созданию одного такого регистра логично положить в отдельный модуль, который затем подключать по мере необходимости.

Теперь покажем, как проводить по регистру документы. Предположим у нас объявлен документ поступления товаров на склад:


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

Рассмотрим более сложный случай, когда объявлен документ перемещения со склада на склад:

Перемещение со склада на склад


Его также нужно провести по этому регистру, но уже дважды. Один раз он должен списать со склада (откуда) перемещенное количество по товару, а второй раз добавить его к складу назначения. Для того, чтобы списать товар будем использовать ту же логику наследования:

Проведение расхода


Так как это расходная операция, то количество берем с минусом, а в качестве склада подставляем склад отправителя.

Так как мы не можем один класс наследовать от другого дважды, то для того, чтобы провести по регистру повторно, создадим агрегированный объект нового класса TransferSkuLedger, который затем наследуем от SkuLedger:


Конструкция AGGR указывает платформе, что для каждой строки перемещения, у которой задан склад назначения, нужно создавать новый объект класса TransferSkuLedger, у которого будет ссылка на исходную строку. При удалении такой строки будет также автоматически удаляться и агрегированный объект.
Остается только реализовать абстрактные свойства для нового класса:
Пользуемся тем, что у вновь созданного класса есть ссылка на исходный, который его породил.

К слову, в 1С с этим есть определенные проблемы, так как строка документа может порождать только одну запись в регистре:

Система обеспечивает контроль уникальности записей, хранящихся в регистре накопления. Благодаря этому в регистре накоплений не может находиться двух записей, относящихся к одной и той же строке одного и того же документа.


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

Регистр сведений


В отличие от регистра накоплений, регистр сведений рассчитывает не сумму показателя, а последнее значение действующее на определенное время.

Объявление такого регистра абсолютно идентично логике регистра накоплений. Построим для примера регистр изменения цены поступления:

Регистр изменения цены поступления


Для расчета последней цены поступления используем следующую конструкцию:
К сожалению, материализовать ее не получится, так как значение этого свойство определено для бесконечного количества точек во времени. Однако, если построить нужный индекс, то обращение будет происходить относительно быстро за логарифмическое время:
В индекс и в порядок добавляется сам регистр, так как, в отличие от 1С, в lsFusion могут быть записи с одинаковым временем. В этом случае, в качестве дополнительного выражения будет использоваться внутренний код записи регистра.

Проведение по регистру сведений идет также, как и в регистре накоплений:


Заключение


Схема регистров в 1С позволяет делать то, что в обычном программировании реализуется при помощи наследования и композиции (там регистр был бы просто интерфейсом). Тем самым, они пытаются в частном случае решить проблему отсутствия этих механизмов в 1С платформе. Хотя, фактически, регистр является интерфейсом, который может реализовывать либо сама строка документа, либо некий агрегированный объект (композиция), созданный на ее основе.

В lsFusion же наследование и полиморфизм реализованы в общем случае, что делает ее значительно более универсальной и применимой в большем количестве случаев. Фактически, в ней можно реализовать любую логику регистров, ведь они отличаются исключительно способом расчета вычисляемых свойств на основе данных из этих регистров.

© Habrahabr.ru