воскресенье, 14 сентября 2014 г.

1C: прикосновение к прекрасному

Давеча пришлось немного покопаться в 1С:Предприятие 8.1. Ну, то есть как "покопаться"... Есть у нас некий АРМ Гаража, написанный на этой платформе. Разработчики давно прекратили поддержку. Доселе всё работало, но, очевидно, рано или поздно должно было начать чудить. В данном случае это проявилось в том, что пара путевых листов перестала корректно учитываться в отчёте "Движение ГСМ". Так как уровень моих познаний в 1С равен нулю, то, думается, будет небесполезно рассказать, как я пытался от этого дна оторваться. Итак...

Ну, во-первых, 1С:Предприятие 8.1 предполагает два режима работы: собственно Предприятие и Конфигуратор. Грубо говоря, парадный и служебный входы. В первом режиме штатно работают пользователи, второй режим позволяет проводить администрирование системы и заниматься её разработкой.

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

Конечно, никто в здравом уме не будет экспериментировать на живой, включенной в производственный процесс базе. Поэтому нужно улучить такой момент, когда с базой никто не работает, и с помощью меню Конфигуратора "Администрирование - Выгрузить информационную базу" выгрузить интересующую нас базу в файл. Затем создаем пустую базу (для этого в комплекте 1С поставляется утилита "Серверы 1С:Предприятие") и с помощью всё того же Конфигуратора через пункт "Администрирование - Загрузить информационную базу" поднимаем из файла копию интересующей нас информационной базы.

Далее задача становится интересной. Для таких дилетантов, как я, которым нельзя доверить правку конфигурации, но поработать с базой хочется, придумали специальную штуку - внешние обработки. Они представляют из себя своеобразные "программы", которые создаются в Конфигураторе, а выполняются в режиме "Предприятие". Например, при помощи внешней обработки за пару секунд можно слепить инструмент выполнения вышеупомянутых sql-подобных запросов к информационной базе.

Делается это так. В Конфигураторе через меню "Файл - Новый..." создаем внешнюю обработку. В открывшемся окне вновь созданной обработки в разделе "Формы" добавляем новую форму, устанавливаем эту форму "основной формой внешней обработки", кидаем на форму поле ввода, табличное поле и кнопку. В результате имеем три элемента интерфейса: ПолеВвода1, ТабличноеПоле1 и Кнопка1. Кроме того, с каждым элементом оказывается связана некая переменная, наименование которой совпадает с наименованием элемента.

Далее на кнопку Кнопка1 вешаем обработчик события нажатия:
Процедура Кнопка1Нажатие(Элемент)

    Запрос = Новый Запрос(); // слева - переменная, справа - класс.
    Запрос.Текст = ПолеВвода1; // справа - переменная, которую 1C создал автоматически вместе с полем ввода
    ТабличноеПоле1 = Запрос.Выполнить().Выгрузить(); // слева - переменная, которую 1C создал вместе с табличным полем
    ЭлементыФормы.ТабличноеПоле1.СоздатьКолонки();

КонецПроцедуры
Сохраняем внешнюю обработку, открываем её 1С:Предприятие, и получаем возможность выполнять запросы к информационной базе.

Запросы на первый взгляд мало чем отличаются от обычных sql-запросов. Ну, двуязычные (можно писать ВЫБРАТЬ...ИЗ... или SELECT...FROM...), но это мелочи. Основное, что бросается в глаза, это работа с джойнами. То есть, если у нас, скажем, есть Документ ПутевойЛист, в котором присутствует ссылка ТранспортноеСредство на соответствующий элемент Справочника ТС, то следующие два запроса вернут один и тот же набор данных:
ВЫБРАТЬ
    ПутевойЛист.ТранспортноеСредство.Номер
ИЗ
    Документ.ПутевойЛист КАК ПутевойЛист
ГДЕ
    ПутевойЛист.Номер = "123"
ВЫБРАТЬ
    ТС.Номер
ИЗ
    Документ.ПутевойЛист КАК ПутевойЛист
    ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ТС КАК ТС
        ПО ПутевойЛист.ТранспортноеСредство = ТС.Ссылка
ГДЕ
    ПутевойЛист.Номер = "123"

Кроме того, в 1С есть конструктор запросов, вызываемый из контекстного меню окна редактирования текста модуля. Т.е., скажем, при написании кода обработчика нажатия кнопки можно вызвать конструктор запроса, немного поработать мышкой и получить готовый текст запроса.

А вот dml в этой системе выглядит немного хитро. Для добавления данных в какую-то таблицу (т.е, регистр учета, справочник и т.п.) приходится сначала открыть выборку по этой таблице, потом добавить в выборку новую запись, в этой записи заполнить нужные поля, и затем выборку записать. Ну, примерно как в дельфях с их датасетами. В процессе заполнения этих самых полей, особенно в случае, когда заполняются внешние ключи на другие таблицы, полезно помнить, что все записи в 1С имеют специальный атрибут Ссылка, который позволяет поля-внешние ключи правильно заполнять.

В моем случае (путевой лист, не желавший учитываться в отчете по движению ГСМ) понадобилось добавить запись, связанную с путевым листом, в один из регистров накопления. Соответствующая процедура получилась вот такой:
Процедура Кнопка2Нажатие(Элемент)

    // вытаскиваем путевой лист в переменные с префиксом ПЛ_...

    Запрос_ПутевойЛист = Новый Запрос();
    Запрос_ПутевойЛист.Текст = 
    "ВЫБРАТЬ
     |    ПутевойЛист.Ссылка,
     |    ПутевойЛист.Дата,
     |    ПутевойЛист.Организация,
     |    ПутевойЛист.ТранспортноеСредство,
     |    ПутевойЛист.ТранспортноеСредство.Колонна КАК Колонна,
     |    ПутевойЛист.Водитель1,
     |    Номенклатура.Ссылка КАК Топливо
     |ИЗ
     |    Документ.ПутевойЛист КАК ПутевойЛист
     |    ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Номенклатура
     |        ПО ВЫРАЗИТЬ(Номенклатура.НаименованиеПолное КАК СТРОКА(11)) = ""Бензин АИ92""
     |ГДЕ
     |    ПутевойЛист.Номер = ""123""
     |";

    РезультатЗапроса_ПутевойЛист = Запрос_ПутевойЛист.Выполнить().Выгрузить();

    Для каждого Запись_ПутевойЛист из РезультатЗапроса_ПутевойЛист Цикл

        ПЛ                      = Запись_ПутевойЛист.Ссылка;
        ПЛ_Дата                 = Запись_ПутевойЛист.Дата;
        ПЛ_Организация          = Запись_ПутевойЛист.Организация;
        ПЛ_Колонна              = Запись_ПутевойЛист.Колонна;
        ПЛ_ТранспортноеСредство = Запись_ПутевойЛист.ТранспортноеСредство;
        ПЛ_Водитель             = Запись_ПутевойЛист.Водитель1;
        ПЛ_Топливо              = Запись_ПутевойЛист.Топливо;
        ПЛ_РасходПоНорме        = 20.38;
        ПЛ_РасходПоФакту        = 20;
        ПЛ_Активность           = Истина;

    КонецЦикла;

    // открываем регистр накопления

    ПЛ_Регистр = РегистрыНакопления.РасходГСМнаТС.СоздатьНаборЗаписей();
    ПЛ_Регистр.Отбор.Регистратор.Установить(ПЛ);
    ПЛ_Регистр.Прочитать();

    // добавляем и заполняем запись в регистр накопления

    НовыйРегистр = ПЛ_Регистр.Добавить();

    НовыйРегистр.Период        = ПЛ_Дата;
    НовыйРегистр.Регистратор   = ПЛ;
    НовыйРегистр.Активность    = ПЛ_Активность;
    НовыйРегистр.Организация   = ПЛ_Организация;
    НовыйРегистр.Колонна       = ПЛ_Колонна;
    НовыйРегистр.ТС            = ПЛ_ТранспортноеСредство;
    НовыйРегистр.Водитель      = ПЛ_Водитель;
    НовыйРегистр.ГСМ           = ПЛ_Топливо;
    НовыйРегистр.РасходПоНорме = ПЛ_РасходПоНорме;
    НовыйРегистр.РасходПоФакту = ПЛ_РасходПоФакту;

    ПЛ_Регистр.Записать(Истина);

    // сообщаем пользователю, что всё прошло успешно

    Сообщить(ПЛ);
    Сообщить("job done");

КонецПроцедуры

В общем, скажу честно, в понимании происходящего мне сильно помогло, что я однажды, сам того не подозревая, уже пытался создать нечто, подобное 1С, и, следовательно, идеология этой системы оказалась довольно знакомой (по крайней мере, азы). Остальной океан непознанного ещё плещется у порога. Затопит - будем разбираться дальше.

Использованные источники:
Как быстро вывести результат Запроса в табличный документ?
Регистры накопления в языке 1С 8.3, 8.2 (в примерах)
Особенности языка запросов 1С