Перейти к основному содержимому
Версия: 7.0

How-to: Нумерация

Нумерация вручную

Предположим есть некоторый набор книг. Для каждой из них определяется номер как целое число.

CLASS Book 'Книга';
number 'Номер' = DATA INTEGER (Book) IN id;
name 'Название' = DATA ISTRING[50] (Book) IN id;

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

book (INTEGER number) = GROUP AGGR Book b BY number(b);

bookExists (INTEGER number) {
IF book(number) THEN
MESSAGE 'Книга с номером ' + number + ' существует. Ее название : ' + name(book(number));
ELSE
MESSAGE 'Книги с номером ' + number + ' не существует';
}

Оператор GROUP AGGR автоматически добавляет ограничение на уникальность номера. При попытки записать в базу данных повторный номер будет выдано сообщение об ошибке.

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

WHEN SET(Book b IS Book) AND NOT number(b) DO {
number(b) <- (GROUP MAX number(Book bb)) (+) 1;
}

Событие будет вызвано в момент сохранения создания книги в базу данных в той же транзакции.

В некоторых ситуациях существует необходимость делать разную нумерацию для одного и того же объекта. Для этой цели можно добавить специальный класс Numerator.

CLASS Numerator 'Нумератор';
name 'Наименование' = DATA ISTRING[50] (Numerator) IN id;

value = DATA INTEGER (Numerator);

В свойстве value будет храниться текущее значение нумератора, которое будет записываться в номер нужного объекта. Чтобы достичь этого, для объекта (например, заказ) задается ссылка на соответствующей нумератор. В момент создания объекта, если она задана, то нужно автоматически проставить номеру заказа текущее значение нумератора и увеличить его на один.

CLASS Order 'Заказ';
number 'Номер' = DATA INTEGER (Order) IN id;

numerator 'Нумератор' = DATA Numerator (Order);
WHEN CHANGED(numerator(Order o)) AND NOT CHANGED(number(o)) DO {
number(o) <- value(numerator(o));
value (Numerator n) <- value(n) (+) 1 WHERE n == numerator(o);
}

В условии события проверяется на то, что номер не был изменен, чтобы не изменять его, если пользователь вручную задал номер (или он проставился при импорте).

Важным отличием работы через нумератор от "проставления максимального значения плюс один" является обработка одновременного добавления объектов. Во втором случае, если два пользователя одновременно создадут объекты, то последнему в момент сохранения будет выдано сообщение о повторении номера, и он будет вынужден вручную сохранить повторно. Изменения, сделанные во всех событиях таким образом, будут "откачены", и повторное сохранение сгенерирует новый номер. В случае с нумератором, транзакция последнего пользователя получит CONFLICT UPDATE на поле value для нумератора (так как обе транзакции меняют поле одной строки в базе данных). Дальше система автоматически откатит транзакцию и все изменения, сделанные в событии, и начнет ее проводить повторно без вмешательства пользователя. Таким образом пользователь заметит лишь возможно в два раза медленнее сохранение, но никаких его дополнительных действий не потребуется.

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

defaultNumerator 'Нумератор по умолчанию' = DATA Numerator();

WHEN SET(Order o IS Order) AND NOT CHANGED(numerator(o)) DO
numerator(o) <- defaultNumerator();

Готовая реализация этого подхода — серия, ведущие нули, нумераторы по умолчанию и защищённая от конфликтов генерация — поставляется с платформой в модуле Numerator. Приведённые ниже сценарии опираются на него; в каждом из них номер проставляется, только если он ещё не задан, поэтому введённое пользователем или загруженное импортом значение никогда не перезаписывается.

Порядковый номер для класса

Предположим, что нужно выдавать каждому объекту класса порядковый номер с фиксированным префиксом-серией.

CLASS Adjustment 'Корректировка';
@defineNumbered(Adjustment, STRING[3]);
@defineNumeratedDefault(Adjustment, 'Корректировки', 'INV');

@defineNumbered добавляет хранимые свойства number и series (здесь серия — строка из трёх символов) и их проиндексированную конкатенацию. @defineNumeratedDefault заводит начальный нумератор с наименованием Корректировки и серией INV, делает его нумератором по умолчанию для класса и проставляет каждому новому объекту. При создании номер заполняется из этого нумератора, а его счётчик продвигается, поэтому первая корректировка получает INV00001, следующая — INV00002 и так далее.

Отдельная последовательность для каждого типа документа

Рассмотрим более сложный случай, когда документ нумеруется по-разному в зависимости от его типа, причём у каждого типа свой счётчик.

Нумератор храним на классе типа и заполняем документ из нумератора его типа.

CLASS OrderType 'Тип заказа';
name 'Наименование' = DATA ISTRING[50] (OrderType);
numerator 'Нумератор' = DATA Numerator (OrderType);

CLASS Order 'Заказ';
@defineNumbered(Order, STRING[3]);
type 'Тип' = DATA OrderType (Order);

Затем добавляем событие, которое, как только выбран тип, берёт очередное значение и серию из нумератора этого типа и продвигает его счётчик.

WHEN SETCHANGED(type(Order o)) AND numerator(type(o)) AND NOT number(o) DO {
number(o) <- curStringValue(numerator(type(o)));
series(o) <- series(numerator(type(o)));
incrementValueSession(numerator(type(o)));
}

Два типа заказа, указывающие на разные нумераторы, дают две независимые последовательности.

Сгенерированный код как идентификатор объекта

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

CLASS Partner 'Контрагент';
id 'Код' = DATA STRING[20] (Partner) IN id;
partner (STRING[20] id) = GROUP AGGR Partner p BY id(p);

@defineNumeratedID(Partner, 'Контрагенты');

@defineNumeratedID добавляет классу нумератор по умолчанию и событие, которое при создании записывает в собственный id объекта серию и дополненное нулями значение счётчика, после чего продвигает счётчик. Свойство id и поиск по нему объявляются как обычно; сгенерированный код сохраняет этот поиск уникальным. Сам нумератор выбирается на форме Справочники > Нумераторы по умолчанию, поэтому администратор может сменить серию или начальное значение без изменения кода.