Рассмотрим распространенную прикладную задачу: необходимо реализовать систему лимитов на количество обслуживаемых объектов или клиентов с контролем по разным периодам одновременно. В качестве классического примера выступает медицинская регистратура, где действуют квоты на прием определенных категорий пациентов (например, по ОМС, ДМС или льготным программам) в разрезе врача, его специальности, отделения и больницы. При этом ограничения задаются сразу на день, неделю и месяц (например, не более 2 пациентов в день, 5 в неделю и 15 в месяц), а при формировании сетки расписания или проведении документа записи система должна контролировать остаток свободного лимита.
Разберем основные архитектурные подходы к проектированию структуры регистров для такой задачи, проанализируем их плюсы и минусы, а также выясним, как оптимизировать запросы и избежать взаимных блокировок.
Наиболее масштабируемым и каноническим подходом для задач бюджетирования и лимитирования в платформе 1С является разделение нормативных ограничений (плана) и фактических записей (факта).
Если попытаться использовать классический регистр остатков, мы столкнемся с проблемой: один и тот же факт приема должен одновременно уменьшать остаток дня, недели и месяца. Это приводит к размножению движений и искусственному дроблению периодов в измерениях, что быстро перегружает базу данных.
Оптимальная схема предполагает использование двух регистров:
Подразделение, Специальность, Сотрудник, СегментПациентов (или группа аналитики), Периодичность (перечисление: День, Неделя, Месяц).
Лимит (Число).Подразделение, Специальность, Сотрудник, СегментПациентов.
Количество (Число).В оборотный регистр движение делается документом записи строго на конкретную дату и время приема. Нам не нужно дублировать запись для недели или месяца — квантом учета выступает день прихода. Суммарный факт за неделю и месяц рассчитывается виртуальной таблицей оборотов за требуемый интервал.
Посмотрим на пример структуры запроса, который собирает фактические обороты за день, неделю и месяц для сопоставления с плановыми квотами:
ВЫБРАТЬ
ТаблицаОборотовДень.Сотрудник КАК Сотрудник,
ТаблицаОборотовДень.СегментПациентов КАК СегментПациентов,
ЕСТЬNULL(ТаблицаОборотовДень.КоличествоОборот, 0) КАК ФактДень,
ЕСТЬNULL(ТаблицаОборотовНеделя.КоличествоОборот, 0) КАК ФактНеделя,
ЕСТЬNULL(ТаблицаОборотовМесяц.КоличествоОборот, 0) КАК ФактМесяц
ПОМЕСТИТЬ ВТ_Факты
ИЗ
РегистрНакопления.ЗаписиПациентов.Обороты(
НАЧАЛОПЕРИОДА(&ДатаПриема, ДЕНЬ),
КОНЕЦПЕРИОДА(&ДатаПриема, ДЕНЬ),
День,
Сотрудник = &Врач И СегментПациентов = &Сегмент) КАК ТаблицаОборотовДень
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ЗаписиПациентов.Обороты(
НАЧАЛОПЕРИОДА(&ДатаПриема, НЕДЕЛЯ),
КОНЕЦПЕРИОДА(&ДатаПриема, НЕДЕЛЯ),
Авто,
Сотрудник = &Врач И СегментПациентов = &Сегмент) КАК ТаблицаОборотовНеделя
ПО ТаблицаОборотовДень.Сотрудник = ТаблицаОборотовНеделя.Сотрудник
И ТаблицаОборотовДень.СегментПациентов = ТаблицаОборотовНеделя.СегментПациентов
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ЗаписиПациентов.Обороты(
НАЧАЛОПЕРИОДА(&ДатаПриема, МЕСЯЦ),
КОНЕЦПЕРИОДА(&ДатаПриема, МЕСЯЦ),
Авто,
Сотрудник = &Врач И СегментПациентов = &Сегмент) КАК ТаблицаОборотовМесяц
ПО ТаблицаОборотовДень.Сотрудник = ТаблицаОборотовМесяц.Сотрудник
И ТаблицаОборотовДень.СегментПациентов = ТаблицаОборотовМесяц.СегментПациентов
;
ВЫБРАТЬ
ЛимитыСрез.Периодичность КАК Периодичность,
ЛимитыСрез.Лимит КАК Лимит,
ВЫБОР
КОГДА ЛимитыСрез.Периодичность = ЗНАЧЕНИЕ(Перечисление.ПериодичностиЛимитов.День)
ТОГДА Факты.ФактДень
КОГДА ЛимитыСрез.Периодичность = ЗНАЧЕНИЕ(Перечисление.ПериодичностиЛимитов.Неделя)
ТОГДА Факты.ФактНеделя
КОГДА ЛимитыСрез.Периодичность = ЗНАЧЕНИЕ(Перечисление.ПериодичностиЛимитов.Месяц)
ТОГДА Факты.ФактМесяц
ИНАЧЕ 0
КОНЕЦ КАК Факт
ИЗ
РегистрСведений.УстановленныеЛимиты.СрезПоследних(&ДатаПриема, Сотрудник = &Врач И СегментПациентов = &Сегмент) КАК ЛимитыСрез
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Факты КАК Факты
ПО ИСТИНА
Сравнивая поля Лимит и Факт, система определяет, доступна ли запись на выбранную дату. Если Факт + 1 > Лимит хотя бы по одной из периодичностей, выводится предупреждение или блокируется запись.
Альтернативный вариант, предложенный в обсуждении, предполагает использование классического регистра накопления остатков. Чтобы реализовать контроль разнородных периодов в одной таблице, в регистр вводят специальное измерение — СчетчикЛимита (ссылка на специализированный справочник или план видов характеристик).
Разберем логику этой модели:
СчетчикиЛимитов формируется динамически или регламентно и инкапсулирует в себе аналитику и конкретный интервал: например, «Иванов_Терапевт_ОМС_Неделя_14_2026».Остаток >= 1 по каждому счетчику.Преимущество: Высокая скорость проверки при проведении документов, так как остаток по счетчику получается мгновенно из итогов регистра остатков без вычисления оборотов за период.
Недостатки: Избыточный рост справочника ключей аналитики (сотни тысяч записей при большом штате сотрудников и длинном горизонте планирования), а также высокая сложность пересчета движений при изменении правил лимитирования или даты приема «задним числом».
При открытии сетки расписания администратор видит календарь на неделю вперед по нескольким десяткам врачей. Если выполнять запрос на расчет лимитов в цикле для каждого слота или дня, система столкнется с критическим падением производительности.
Проанализируем правила оптимизации этой операции:
НАЧАЛОПЕРИОДА(ТаблицаДат.Дата, НЕДЕЛЯ) и НАЧАЛОПЕРИОДА(ТаблицаДат.Дата, МЕСЯЦ). Это позволит соединить данные календаря с виртуальной таблицей оборотов за один пакетный запрос на всю отображаемую сетку.Когда квота задается не только на конкретного врача, но и на отделение в целом (например, врач принимает не более 2 льготников в день, но все терапевты отделения суммарно не более 6), аналитику измерений в регистре сведений настраивают с возможностью незаполненных значений:
Если лимит общий на подразделение, измерение Сотрудник оставляют пустым. Проверка выполняется каскадно:
При одновременной записи пациентов несколькими регистраторами возникает риск состояния гонки (race condition): два оператора одновременно прочитают доступный остаток в 1 талон и проведут документы, что приведет к превышению лимита.
Чтобы избежать этой коллизии, в транзакции записи документа необходимо выставлять управляемую блокировку данных:
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ЗаписиПациентов");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.УстановитьЗначение("Сотрудник", Врач);
ЭлементБлокировки.УстановитьЗначение("СегментПациентов", Сегмент);
Блокировка.Заблокировать();
// Далее выполняется запрос контроля лимитов и запись движений
Такой механизм гарантирует целостность квот при высокой интенсивности работы пользователей регистратуры.