Мы сталкиваемся с распространенной задачей в 1С: необходимо, чтобы при проведении документа ВыдачаКниги, в справочник Книги в реквизит Статус записывалось значение "Выдана". Давайте разберем, почему такой подход не является оптимальным и как следует реализовать подобную функциональность в системе 1С.
Начнем с анализа первоначальной задачи: "При проведении документа ВыдачаКниги, в справочник Книги в реквизит Статус записывалось "Выдана"". На первый взгляд это может показаться простым решением, но опытные разработчики 1С справедливо указывают, что прямое изменение реквизита справочника при проведении документа является плохой практикой. Рассмотрим подробнее причины:
Поэтому мы с вами будем искать более правильный, архитектурно верный и гибкий подход.
Для решения подобных задач в 1С существуют специализированные объекты — регистры. Они идеально подходят для хранения изменяемых данных, связанных с документами, и позволяют получать информацию на любой момент времени, эффективно обрабатывать движения и строить отчеты. В зависимости от специфики учета, нам могут подойти:
Опираясь на обсуждение в теме и правила библиотечного учета (как было отмечено в сообщении 19 и дополнительной информации), где говорится о пообъектном учете каждого экземпляра (то есть каждой физической книги присваивается уникальный инвентарный номер и отслеживается ее собственная история), более предпочтительным и полным является использование Регистра накоплений. Этот регистр позволит нам точно отслеживать, где находится каждый конкретный экземпляр книги.
Регистр накоплений типа "Остатки" идеально подходит для нашей задачи. Он позволит нам:
Прежде чем перейти к реализации, определим необходимую структуру данных в нашей конфигурации 1С:
Книги: Мы будем использовать этот справочник для хранения информации об уникальных экземплярах книг. Это означает, что каждый элемент справочника Книги будет соответствовать одной физической копии книги. В этом справочнике будут храниться такие метаданные, как Название, Автор, ISBN, ГодИздания, а также критически важный реквизит ИнвентарныйНомер, который будет уникально идентифицировать каждый экземпляр.Читатели: Этот справочник будет хранить информацию о лицах, которым выдаются книги (ФИО, контактные данные и т.п.).ВыдачаКниги: Этот документ будет регистрировать факт выдачи одной или нескольких книг читателю.ВозвратКниги: Этот документ будет регистрировать факт возврата книги от читателя в библиотеку.МестаХраненияКниг: Для удобства мы создадим перечисление со значениями, такими как "Библиотека", "НаРукахУЧитателя", "ВРемонте", "НаРеставрации" и т.п.НаличиеКниг: Этот регистр будет хранить оперативную информацию о текущем местонахождении каждого экземпляра книги.НаличиеКнигМы создадим новый регистр накоплений с именем НаличиеКниг. Установим его вид как "Остатки", так как нам необходимо отслеживать фактическое наличие и местонахождение книг. Он будет иметь следующую структуру:
Книга (Тип: СправочникСсылка.Книги) - Это измерение будет ссылаться на конкретный экземпляр книги, позволяя нам отслеживать каждый уникальный физический объект.МестоНахождения (Тип: ПеречислениеСсылка.МестаХраненияКниг) - Это измерение покажет, где именно находится книга.Читатель (Тип: СправочникСсылка.Читатели) - Если книга находится "НаРукахУЧитателя", это измерение позволит узнать, у какого именно читателя. Для других мест хранения это измерение может быть незаполненным.Количество (Тип: Число, Длина 1, Точность 0, Неотрицательное) - Для пообъектного учета значение этого ресурса всегда будет 1. Оно показывает, что один экземпляр книги находится в указанном месте.Не забудьте установить для документа ВыдачаКниги и ВозвратКниги (который мы создадим позже) признак "Регистратор" для регистра НаличиеКниг.
ВыдачаКнигиДокумент ВыдачаКниги должен иметь табличную часть, в которой указываются выдаваемые книги и читатель.
Дата (Тип: Дата) - Дата выдачи.ВыдаваемыеКниги:
Книга (Тип: СправочникСсылка.Книги) - Ссылка на конкретный экземпляр книги, который выдается.Читатель (Тип: СправочникСсылка.Читатели) - Ссылка на читателя, которому выдается книга.ВыдачаКнигиВ обработчике проведения документа ВыдачаКниги (процедура ОбработкаПроведения()) мы будем формировать движения по регистру накоплений НаличиеКниг. Предположим, что в документе есть табличная часть ВыдаваемыеКниги, которая содержит колонки Книга и Читатель.
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
// Устанавливаем режим записи движений для регистра НаличиеКниг
Движения.НаличиеКниг.Записывать = Истина;
// Очищаем существующие движения, если документ перепроводится
Движения.НаличиеКниг.Очистить();
Для Каждого СтрокаТЧ Из ВыдаваемыеКниги Цикл
// Шаг 1: Проверяем, что книга доступна в библиотеке
// Мы используем запрос к виртуальной таблице остатков регистра
// для проверки наличия книги в нужном месте.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| НаличиеКнигОстатки.Книга КАК Книга,
| НаличиеКнигОстатки.КоличествоОстаток КАК Количество
|ИЗ
| РегистрНакоплений.НаличиеКниг.Остатки(
| &МоментВремени,
| Книга = &Книга
| И МестоНахождения = &МестоНахожденияВБиблиотеке
| ) КАК НаличиеКнигОстатки";
Запрос.УстановитьПараметр("МоментВремени", МоментВремени()); // Момент времени проведения документа
Запрос.УстановитьПараметр("Книга", СтрокаТЧ.Книга);
Запрос.УстановитьПараметр("МестоНахожденияВБиблиотеке", Перечисления.МестаХраненияКниг.Библиотека);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Если НЕ Выборка.Следующий() Тогда
// Если книга не найдена в библиотеке, значит ее там нет или она уже выдана
Отказ = Истина;
Сообщить("Экземпляр книги " + СтрокаТЧ.Книга.ИнвентарныйНомер + " ('" + СтрокаТЧ.Книга.Наименование + "') отсутствует в библиотеке или уже находится не в фонде!");
Продолжить; // Переходим к следующей строке табличной части
КонецЕсли;
// Если книга найдена, и ее количество (для пообъектного учета это 1) больше 0,
// то мы можем ее выдать.
// Шаг 2: Формируем движения регистра
// Движение Расход: книга выбывает из библиотеки
Движение = Движения.НаличиеКниг.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Расход; // Книга выбывает
Движение.Период = Дата; // Дата документа
Движение.Книга = СтрокаТЧ.Книга;
Движение.МестоНахождения = Перечисления.МестаХраненияКниг.Библиотека;
Движение.Количество = 1; // Учитываем по экземплярам
// Движение Приход: книга поступает читателю
Движение = Движения.НаличиеКниг.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Приход; // Книга прибывает к читателю
Движение.Период = Дата;
Движение.Книга = СтрокаТЧ.Книга;
Движение.МестоНахождения = Перечисления.МестаХраненияКниг.НаРукахУЧитателя;
Движение.Читатель = СтрокаТЧ.Читатель; // Указываем конкретного читателя
Движение.Количество = 1;
КонецЦикла;
КонецПроцедуры
Важный момент: Мы видим, что в коде нет прямого обращения к справочнику Книги для изменения реквизита Статус. Все изменения состояния книги фиксируются в регистре накоплений.
ВозвратКнигиДля завершения цикла учета нам потребуется создать документ ВозвратКниги. Его структура будет аналогична документу ВыдачаКниги, но движения в обработке проведения будут обратными:
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
Движения.НаличиеКниг.Записывать = Истина;
Движения.НаличиеКниг.Очистить();
Для Каждого СтрокаТЧ Из ВозвращаемыеКниги Цикл // Предполагаем табличную часть ВозвращаемыеКниги
// Шаг 1: Проверяем, что книга находится на руках у данного читателя
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| НаличиеКнигОстатки.Книга КАК Книга,
| НаличиеКнигОстатки.КоличествоОстаток КАК Количество
|ИЗ
| РегистрНакоплений.НаличиеКниг.Остатки(
| &МоментВремени,
| Книга = &Книга
| И МестоНахождения = &МестоНахожденияУЧитателя
| И Читатель = &Читатель
| ) КАК НаличиеКнигОстатки";
Запрос.УстановитьПараметр("МоментВремени", МоментВремени());
Запрос.УстановитьПараметр("Книга", СтрокаТЧ.Книга);
Запрос.УстановитьПараметр("МестоНахожденияУЧитателя", Перечисления.МестаХраненияКниг.НаРукахУЧитателя);
Запрос.УстановитьПараметр("Читатель", СтрокаТЧ.Читатель); // Читатель из табличной части
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Если НЕ Выборка.Следующий() Тогда
Отказ = Истина;
Сообщить("Экземпляр книги " + СтрокаТЧ.Книга.ИнвентарныйНомер + " ('" + СтрокаТЧ.Книга.Наименование + "') не находится на руках у читателя " + СтрокаТЧ.Читатель.Наименование + "!");
Продолжить;
КонецЕсли;
// Шаг 2: Формируем движения регистра
// Движение Расход: книга выбывает от читателя
Движение = Движения.НаличиеКниг.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
Движение.Период = Дата;
Движение.Книга = СтрокаТЧ.Книга;
Движение.МестоНахождения = Перечисления.МестаХраненияКниг.НаРукахУЧитателя;
Движение.Читатель = СтрокаТЧ.Читатель;
Движение.Количество = 1;
// Движение Приход: книга поступает в библиотеку
Движение = Движения.НаличиеКниг.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Приход;
Движение.Период = Дата;
Движение.Книга = СтрокаТЧ.Книга;
Движение.МестоНахождения = Перечисления.МестаХраненияКниг.Библиотека;
Движение.Количество = 1;
КонецЦикла;
КонецПроцедуры
Хотя Регистр накоплений лучше подходит для учета движения и остатков экземпляров, Регистр сведений может быть полезен для хранения дополнительных, периодически изменяемых характеристик книги, которые не являются остатками. Например, если бы нам нужно было отслеживать не просто "Выдана/Доступна", а более сложные состояния или свойства, не связанные с количественным учетом, например, "Ветхая", "Требует ремонта", "На реставрации".
Если бы первоначальная задача была строго о хранении только статуса ("Выдана" / "Доступна") без детализации по конкретным экземплярам и количеству, Регистр сведений мог бы быть использован. Мы могли бы создать периодический Регистр сведений СтатусыЭкземпляровКниг.
СтатусыЭкземпляровКниг (периодический)Книга (Тип: СправочникСсылка.Книги) - Ссылка на экземпляр книги.Статус (Тип: ПеречислениеСсылка.СтатусыКниг) - Например, "Доступна", "Выдана", "ВРемонте". Создадим новое перечисление СтатусыКниг.Читатель (Тип: СправочникСсылка.Читатели) - Если статус "Выдана", то указываем кому.В этом случае при проведении документа ВыдачаКниги мы бы делали движения по этому регистру сведений, записывая новый статус и читателя:
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
// ... (возможно, проверка на доступность книги через этот же регистр) ...
Движения.СтатусыЭкземпляровКниг.Записывать = Истина;
Движения.СтатусыЭкземпляровКниг.Очистить();
Для Каждого СтрокаТЧ Из ВыдаваемыеКниги Цикл
Движение = Движения.СтатусыЭкземпляровКниг.Добавить();
Движение.Период = Дата; // Момент проведения документа
Движение.Книга = СтрокаТЧ.Книга;
Движение.Статус = Перечисления.СтатусыКниг.Выдана;
Движение.Читатель = СтрокаТЧ.Читатель;
КонецЦикла;
КонецПроцедуры
И при возврате книги, статус менялся бы на "Доступна", а читатель очищался.
Однако, как мы уже отметили, Регистр накоплений обеспечивает более полный учет движения и наличия, что обычно требуется для библиотечных систем.
В ходе обсуждения были затронуты и другие важные аспекты библиотечного фонда. Рассмотрим, как мы могли бы учесть их в нашей системе:
Книги (или в отдельный Регистр сведений, если нужно хранить историю изменений состояния) реквизиты, такие как КоэффициентИзноса (число от 0 до 1) или ФизическоеСостояние (перечисление: "Отличное", "Хорошее", "Удовлетворительное", "Ветхое").РеставрацияКниги. Этот документ будет выполнять следующие действия:
НаличиеКниг будет выполняться движение Расход из текущего места нахождения (например, "Библиотека") и Приход в новое местонахождение ("ВРемонте" или "НаРеставрации").КоэффициентИзноса или ФизическоеСостояние в справочнике Книги (если это единоразовая операция без истории) или делать запись в Регистр сведений СостояниеКниг (если нам нужна история изменений состояния).СписаниеКниг, который делает движение Расход из Регистра накоплений НаличиеКниг и может изменять статус в справочнике Книги (если такой реквизит всё же будет) на "Списана".Книги мы создаем реквизит ИнвентарныйНомер (Тип: Строка или Число), и устанавливаем ему свойство "Индексировать" с "Уникальность" для быстрого поиска и гарантии уникальности.ВыдачаКниги или ВозвратКниги можно использовать поиск по инвентарному номеру.ISBN (International Standard Book Number) является международным идентификатором издания, а не конкретного экземпляра. Его можно добавить в справочник Книги как отдельный реквизит (Тип: Строка) для информационной цели, поиска по изданиям и интеграции с внешними системами. Однако ISBN не может заменить ИнвентарныйНомер для пообъектного учета внутри библиотеки.Всегда помните, что каждый дополнительный аспект учета должен быть оправдан реальной потребностью бизнеса и окупать затраты на его ведение и поддержку. В большинстве случаев для базового библиотечного учета достаточно отслеживания движения экземпляров через Регистр накоплений.
Мы выяснили, что прямое изменение реквизита справочника в обработке проведения документа является неверным и неэффективным подходом. Вместо этого, для учета выдачи и статуса книг в 1С следует использовать Регистры накоплений (для учета движения и остатков экземпляров) и, при необходимости, Регистры сведений (для хранения периодически изменяемых характеристик). Этот подход обеспечивает гибкость, возможность получения данных на любую дату, корректное ведение истории движения каждого экземпляра книги и соответствует лучшим практикам разработки в 1С. Реализуя систему таким образом, мы создаем надежную и масштабируемую основу для библиотечного учета.