В процессе автоматизации торгового учета программисты часто сталкиваются с ситуацией, когда у одной и той же номенклатурной позиции может быть несколько штрихкодов. Это абсолютно нормальная практика: производитель может изменить дизайн упаковки и выпустить новую партию с другим штрихкодом, или на складе временно сосуществуют одинаковые товары из разных поставок. Проблема возникает тогда, когда заказчик просит вывести в отчет только самый последний (актуальный) установленный штрихкод.
Если мы заглянем в структуру метаданных большинства типовых конфигураций прошлых поколений, то увидим, что регистр сведений ШтрихкодыНоменклатуры является непериодическим. Это означает, что записи в нем не привязаны ко времени. База данных хранит их как неупорядоченный набор, и просто так «спросить» у СУБД, какая запись была внесена последней, стандартным запросом невозможно. Выясним, какие существуют пути решения этой задачи на платформе 1С:Предприятие 8, начиная от штатных возможностей современных систем и заканчивая грамотной доработкой архитектуры.
Если вы работаете в современных конфигурациях на управляемых формах (например, 1С:Управление нашей фирмой 3.0, 1С:Розница 3.0, 1С:Управление торговлей 11.5, 1С:Комплексная автоматизация 2.5 или 1С:ERP), то разработчики фирмы 1С уже добавили изящный обходной путь для этой проблемы.
Проанализируем этот механизм. В карточке номенклатуры на вкладке со штрихкодами присутствует признак (флаг) «Использовать для отчетов и печатных форм». При добавлении нового штрихкода вручную или через пакетную обработку загрузки система автоматически устанавливает этот флаг для самой свежей записи, снимая его с предыдущих. В структуре регистра сведений за это отвечает реквизит с типом Булево (например, ИспользоватьДляОтчетов).
В этом случае задача в запросе решается максимально просто и быстро с помощью левого соединения:
ВЫБРАТЬ
Номенклатура.Ссылка КАК Товар,
Штрихкоды.Штрихкод КАК ПоследнийШтрихкод
ИЗ
Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ШтрихкодыНоменклатуры КАК Штрихкоды
ПО Номенклатура.Ссылка = Штрихкоды.Номенклатура
И Штрихкоды.ИспользоватьДляОтчетов = ИСТИНА
Этот способ является наиболее приоритетным, так как он не требует изменения структуры конфигурации и работает на штатных индексах СУБД.
Рассмотрим ситуацию, когда штрихкоды генерируются непосредственно внутри вашей базы 1С (это касается штучных или весовых кодов, обычно начинающихся на «20...» или «29...»). Внутренние штрихкоды стандарта EAN-13 присваиваются системой строго последовательно, по возрастанию их числового значения.
В такой ситуации мы можем применить математическую логику: штрихкод с максимальным числовым (или строковым при одинаковой длине) значением и будет являться самым последним созданным кодом. Посмотрим на пример построения такого запроса:
ВЫБРАТЬ
Штрихкоды.Номенклатура КАК Номенклатура,
МАКСИМУМ(Штрихкоды.Штрихкод) КАК ПоследнийШтрихкод
ИЗ
РегистрСведений.ШтрихкодыНоменклатуры КАК Штрихкоды
ГДЕ
Штрихкоды.Штрихкод ПОДОБНО "20%" // Ограничиваем выборку только внутренними кодами
СГРУППИРОВАТЬ ПО
Штрихкоды.Номенклатура
Важное ограничение метода: данный способ абсолютно неприменим, если вы сканируете внешние штрихкоды производителей. У разных производителей структура кодов никак не связана с хронологией их заведения в вашу базу, и штрихкод, начинающийся на «40...», заведенный сегодня, будет меньше штрихкода на «46...», который был заведен три года назад.
Иногда на форумах предлагают «оригинальное» решение: брать последний штрихкод из недавно проведенных документов поступления товаров, реализаций или документов установки цен. Давайте разберем по шагам, почему этот подход технически неверен и опасен для производительности системы:
Если перед вами стоит разовая аналитическая задача (например, нужно срочно восстановить хронологию заведения штрихкодов в базу за прошлый месяц, чтобы исправить ошибку оператора), можно прибегнуть к чтению системного лога — Журнала регистрации.
Журнал регистрации фиксирует все транзакции создания и изменения записей в базе данных с точностью до секунды. Мы можем программно прочитать эти события. Рассмотрим пример кода:
Отбор = Новый Структура;
Отбор.Вставить("Событие", "_$Data$_.New"); // Фильтр на создание новых записей
Отбор.Вставить("Метаданные", Метаданные.РегистрыСведений.ШтрихкодыНоменклатуры);
ТаблицаСобытий = Новый ТаблицаЗначений;
ВыгрузитьЖурналРегистрации(ТаблицаСобытий, Отбор, "Дата, Данные, ПредставлениеДанных");
// Полученную таблицу значений далее можно связать с номенклатурой по GUID данных
Почему это нельзя использовать в отчетах? Метод чтения журнала регистрации крайне ресурсоемок. Он выполняется во внешних файлах на диске, не кэшируется СУБД и не может быть интегрирован внутрь обычного запроса схемы компоновки данных (СКД).
Если вы работаете в нетиповой или старой конфигурации, где штатного признака «для отчетов» нет, а штрихкоды заносятся разные (и внутренние, и внешние), то единственным надежным инженерным решением будет минимальная доработка системы.
Сразу определимся: делать непериодический регистр периодическим не рекомендуется. Изменение структуры ключевых полей регистра сведений может нарушить работу существующих механизмов конфигурации, усложнить будущие обновления и негативно сказаться на производительности при записи данных.
Вместо этого мы добавим в регистр сведений ШтрихкодыНоменклатуры обычный реквизит ДатаСоздания с типом Дата (состав даты: Дата и время).
Разберем по шагам, как автоматизировать заполнение этого поля:
ШтрихкодыНоменклатуры.ДатаСоздания (тип: Дата).ПередЗаписью, чтобы дата фиксировалась автоматически при создании записи:
Процедура ПередЗаписью(Отказ, Замещение)
Для Каждого Запись Из ЭтотОбъект Цикл
Если НЕ ЗначениеЗаполнено(Запись.ДатаСоздания) Тогда
Запись.ДатаСоздания = ТекущаяДатаСеанса();
КонецЕсли;
КонецЦикла;
КонецПроцедуры
После сохранения изменений у всех новых штрихкодов будет автоматически фиксироваться точное время их создания. Для старых записей можно написать простую обработку, которая заполнит это поле датой по умолчанию.
Теперь задача получения последней записи легко и красиво решается в любом отчете СКД с помощью классического среза последних через вложенный запрос:
ВЫБРАТЬ
МаксимальныеДаты.Номенклатура КАК Номенклатура,
Штрихкоды.Штрихкод КАК ПоследнийШтрихкод
ИЗ
(ВЫБРАТЬ
ШтрихкодыМакс.Номенклатура КАК Номенклатура,
МАКСИМУМ(ШтрихкодыМакс.ДатаСоздания) КАК МаксДата
ИЗ
РегистрСведений.ШтрихкодыНоменклатуры КАК ШтрихкодыМакс
СГРУППИРОВАТЬ ПО
ШтрихкодыМакс.Номенклатура) КАК МаксимальныеДаты
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ШтрихкодыНоменклатуры КАК Штрихкоды
ПО МаксимальныеДаты.Номенклатура = Штрихкоды.Номенклатура
И МаксимальныеДаты.МаксДата = Штрихкоды.ДатаСоздания
Используя этот подход, вы получите абсолютно точные данные в отчетах без малейшей потери производительности системы.