Если имеется крайняя необходимость, например, скрыть от определенной группы пользователей некоторые документов (по виду, дате, набору реквизитов и пр.), то одни реализуют алтернативные журналы документов (на основе ТЗ, ТП или даже в виде отчетов), другие задаются философским китайским вопрос (и отказываются от этой затеи), а мы пойдем "прямым" путем...
Начальные условия:
0. Имеется в распоряжении MS SQL Server 2k/2k5 - обязательная часть;
1. Имеется желание - необязательная часть;
2. Имеются sql-знания - крайне желательная часть, но может и без них проканать;
Суть идеи:
Необходимо вмешаться в процесс передачи запроса от v77-клиента на sql-сервер и внести изменения в текст запроса так, чтобы он возвращал все документы, кроме Банковских выписок. Начнем с конца...
V77-клиент для журнала документов формирует запрос к таблице [_1SJOURN].
Для типовой 1С:Бухгалтерии v5.x структура таблицы [_1SJOURN] следующая (смотрим сами так: EXEC sp_help '_1SJOURN'):
- ROW_ID - первичный ключ;
- IDJOURNAL - идентификатор журнала;
- IDDOC - идентификатор документа;
- IDDOCDEF - идентификатор вида документа;
- APPCODE - битовая маска, определяющая какие компоненты использует док-т;
- DATE_TIME_IDDOC - композитное поле из даты, времени и идентификатора документа;
- DNPREFIX - префикс номера документа;
- DOCNO - номер документа;
- CLOSED - битовая маска состояния документа;
- ISMARK - пометка удаления документа;
- ACTCNT - счетчик движений документа;
- VERSTAMP - счетчик изменений документа;
И запрос формируемый v77-клиентом, при открытии журнала документов (в общем случае):
Select * from _1SJOURN(NOLOCK) (1)
Чтобы отсеить Банковские выписки, нужно наложить условие на вид документа (IDDOCDEF). Для Банковской выписки идентификатор вида документа = 238. Что в общем виде должно быть так:
Select * from _1SJOURN(NOLOCK) WHERE IDDOCDEF != 238 (2)
Некоторые могут спросить, а почему "не равно 238", но мы ведь хотим видеть все документы, кроме документов вида Банковская выписка.
От общего вида переходим к конкретным деталям - на самом деле v77-клиент формирует запросы сложнее, чем указано выше, а реализовывать свой sql-парсер, который универсально сможет подменить оригинальный текст запроса на модифицированный - дело не совсем тривиальное. Значит упростим себе задачу, и воспользуемся возможностями sql-сервера - VIEW (представление) нам поможет. Обернем запрос (2) в представление с названием [vw_oops_1SJOURN] и, забегая вперед, подменим оригинальные запросы к таблице [_1SJOURN] на запросы к представлению [vw_oops_1SJOURN], и будем наслаждаться. Но, некоторые могут усомниться, что не взлетит запись (с чтением проблем не будет же), а некоторые не будут в этом сомневаться, но будут уверены, что в конкурентном окружении будут проблемы.
Скрипт на создание представления:
CREATE VIEW [dbo].[vw_oops_1SJOURN]
AS
SELECT
NULLIF(ROW_ID, NULL) as ROW_ID,
IDJOURNAL,
IDDOC,
IDDOCDEF,
APPCODE,
DATE_TIME_IDDOC,
DNPREFIX,
DOCNO,
CLOSED,
ISMARK,
ACTCNT,
VERSTAMP
FROM [dbo].[_1SJOURN]
WHERE IDDOCDEF != 238
Давайте разбираться...
С чтением (SELECT) проблем быть не должно, хинты типа NOLOCK, вставляемые v77-клиентом в запросах, по прежнему будут, т.е. на сервер будут уходить запросы типа: Select * from vw_oops_1SJOURN(NOLOCK).
Запись (INSERT/UPDATE) тоже будет работать, так как представление vw_oops_1SJOURN является обновляемым: используется одна базовая таблица, нет вычисляемых полей, IDENTITY-поле ROW_ID обернуто в NULLIF.
Остается вопрос с хранимыми процедурами, сгенерированными v77-клиентом, и эскалациями блокировок, но для нашего примера это не актуально (было бы актуально, если бы мы переопределили таблицу [_1SJOURN] на какую-либо свою и/или на представление, основанное на другой базовой таблице). Однако, хранимую процедуру [_1sp__1SJOURN_ByIDDOC] переопределить желательно (хоть и не обязательно, на тот случай, если нужно закрутить гайки по-максимуму) на [vw_oops_1sp__1SJOURN_ByIDDOC]:
CREATE PROCEDURE [dbo].[vw_oops_1sp__1SJOURN_ByIDDOC](@id CHAR(9))
AS
SELECT *
FROM vw_oops_1sjourn(NOLOCK)
WHERE IDDOC=@id
GO
C sql-частью определились, теперь возвращаемся к v77-клиенту. Штатно переопределить источник данных нельзя, значит нужно использовать нештатные методы для этого. Не останавливаясь на деталях проектирования и реализации внешней компоненты, продоставляющей функционал подмены запросов, переходим к финальной части - описанию ее программного интерфейса.
Для нашей задачи необходимо создать экземпляр класса:
oQueryAct = СоздатьОбъект("QueryAct");
Пока объект "жив" (находится в области видимости), функционал манипулирования с запросами будет работать, как только объект разрушен - начинается штатная жизнь. Отсюда первое правило: если функционал нужен в течение всего сеанса работы пользователя - создавай глобальную экспортную переменную.
Дальше необходимо заполнить коллекцию базовых объектов, над которыми будут выполняться манипуляции:
Манипуляторы = oQueryAct.Манипуляторы; // коллекция, которую будем заполнять
Заполнение коллекции (переопределение таблицы [_1SJOURN]):
Манипулятор = Манипуляторы.Добавить("_1sjourn");
Манипулятор.Поведение = 2; // поведение манипулятора
Манипулятор.Суррогат = "vw_oops_1sjourn"; // суррогатный объект, которым будет подменен оригинальный объект, в данном случае _1sjourn
Заполнение коллекции (переопределениехранимой процедуры [_1sp__1SJOURN_ByIDDOC]):
Манипулятор = Манипуляторы.Добавить("_1sp__1SJOURN_ByIDDOC");
Манипулятор.Поведение = 2;
Манипулятор.Суррогат = "vw_oops_1sp__1SJOURN_ByIDDOC";
И финишный аккорд (активация манипуляторов):
oQueryAct.Применить();
И пока объект oQueryAct будет "жив", будет выполняться подмена объектов на суррогаты.
Программный интерфейс класса "QueryAct":
Методы:
- Применить / Employ - задействовать новые правила, описанные в коллекции Манипуляторы;
Свойства:
- Манипуляторы / Handlers - коллекция правил, описывающая новое поведение обращения к источникам данных;
Элемент коллекции:
Свойства:
- Поведение / Behaviour (Число) - поведение манипулятора. Возможные значения: 1 - вырезать INDEX-хинты, 2 - заменить имя источника данных на суррогат;
- Суррогат / Substitute (Строка) - имя суррогатного объекта. Имеет смысл указывать при выбранном Поведении = 2;
Работа с коллекцией:
- Добавить / Add - добавить элемент в коллекцию;
- Удалить / Remove (Число/Строка) - удалить элемент из коллекции;
- Очистить / Clear - очистить коллекцию;
- Количество / Count - получить количество элементов в коллекции;
- Получить / Get (Число/Строка) - получить элемент коллекции;
Disclaimer:
Возможно, некоторым нужно будет установить VC runtime library, которую компания Microsoft любезно выложила у себя и совершенно бесплатно предлагает к скачиванию и распространению...
Продолжение следует: а в следующей серии мы поговорим о секционировании!