Книга знаний

1С:Предприятие / v8 / Объекты конфигурации / Документы

v8: Универсальный контроль остатков при проведении/отмены проведения документов

При разработке собственных документов товародвижения (или при доработке существующих), как правило, приходиться писать код для проверки остатков в регистрах накопления. Предлагается использовать три универсальных глобальных процедуры, которые гарантировано проверяют перерасход по регистрам. ПРИ ЭТОМ - ПРОВЕРКА НЕ ЗАВЯЗАНА НА СТРУКТУРУ И СОДЕРЖАНИЕ ТАБЛИЧНОЙ ЧАСИ ДОКУМЕНТА! Что надо сделать: - Добавить прилогаемый ниже код в модуль приложения; - Добавить немного кода в модуль набора записей регистров накопления, по которым неоходимо контролировать остатки. Методика использования процедур описана в комментарии текста программыАвтор статьи: Nafanail | Редакторы: Михей, aa_214,
Последняя редакция №5 от 22.11.07 | История
URL: http://kb.mista.ru/article.php?id=609

Ключевые слова: Документы, Контроль остатков


================ ЭТО НАДО ВСТАВИТЬ В МОДУЛЬ ПРИЛОЖЕНИЯ ===============================

Перем ПроводитьБезПроверки;

Функция глПроводитьБезПроверки(ИстинаЛожь = Неопределено) Экспорт
   Если ИстинаЛожь <> Неопределено Тогда
       ПроводитьБезПроверки = ИстинаЛожь;
   КонецЕсли;
   Возврат ПроводитьБезПроверки;
КонецФункции

Функция глПустаяИлиНеопределено(Ссылка) Экспорт
   Если Ссылка = Неопределено ИЛИ Ссылка.Пустая() Тогда
       Возврат Истина;
   КонецЕсли;
   Возврат Ложь;
КонецФункции

////////////////////////////////////////////////////////////////////////////////////////////////
//                    Процедуры проверки движений документов              //
////////////////////////////////////////////////////////////////////////////////////////////////
// Суть проверки: Проведение, перепроведение и отмена проведения не должны приводить
//        к возникновению отрицательного актуального остатка (ресурс Количество).
// Порядок использования процедур достаточно прост - в модуле объекта документа:
//
// Процедура ПередЗаписью(...)
//    глЗапомнитьСтарыеДвижения(ЭтотОбъект [, Движения.<ИмяРегистраНакопления>]);
// КонецПроцедуры;
//
// Процедура ОбработкаПроведения(...)
//       Смело делаем все необходимые движения
//       и перед выходом из процедуры:
//    глПроверкаПроведения(ЭтотОбъект, Отказ [, Движения.<ИмяРегистраНакопления>]);
// КонецПроцедуры;
//
// Процедура ОбработкаУдаленияПроведения(...)
//    глПроверкаУдаленияПроведения(ЭтотОбъект, Отказ [, Движения.<ИмяРегистраНакопления>]);
// КонецПроцедуры;
//
//    Если указан последний необязательный параметр - проверка будет производится только по указанному
// регистру движений. Иначе проверка будет производиться по всем регистрам движений.
// В модуле набора записей регистра накопления должны быть определены экспортируемые переменные
// и процедуры (см. далее). Если регистр не удовлетворяет указанным
// требования - проверка по этому регистру не будет производиться.
//
// Если все же пользователю крайне необходимо выполнить проведение документа:
//      1. в табло: глПроводитьБезПроверки(Истина)
//    2. проводим документ(ы)
//      3. в табло: глПроводитьБезПроверки(Ложь)
//
//    Диагностика. Во время проверки при обнаружении перерасхода будет выдано в окно сообщений стандартная
// диагностика. Однако эту диагностику можно переопределить. Для этого необходимо в модуле объекта документа
// определить процедуру:
// Сообщить(НабЗаписей, стрТЗ, Остаток) Экспорт
//        НабЗаписей - набор записей регистра нгакопления по которому обнаружен перерасход
//        стрТЗ - строка таблицы значений со значениями измерений
//        Остаток - прогнозируемый актуальный остаток в случае проведения/перепрведения/удаленияПроведения

Процедура глЗапомнитьСтарыеДвижения(Регистратор, НабЗаписей = Неопределено) Экспорт
Перем рег;

   Если НабЗаписей <> Неопределено Тогда
       НабЗаписей.ЗапомнитьСтарыеДвижения();
   Иначе
       Для Каждого рег Из Регистратор.Движения Цикл
           Попытка
               рег.ЗапомнитьСтарыеДвижения();
           Исключение
               Продолжить;
           КонецПопытки;
       КонецЦикла;
   КонецЕсли;
КонецПроцедуры

Процедура глПроверкаПроведения(Регистратор, Отказ, НабЗаписей = Неопределено) Экспорт
Перем рег, стрСвертки;

   Если глПроводитьБезПроверки() Тогда
       Возврат;
   КонецЕсли;

   Если НабЗаписей <> Неопределено Тогда
       стрСвертки = НабЗаписей.стрИзмерения + ",НомерСтроки";
       ПроверкаПроведения(НабЗаписей, Отказ, стрСвертки, Регистратор);
   Иначе
       Для Каждого рег Из Регистратор.Движения Цикл
           Попытка
               стрСвертки = рег.стрИзмерения + ",НомерСтроки";
           Исключение
               Продолжить;
           КонецПопытки;
           ПроверкаПроведения(рег, Отказ, стрСвертки, Регистратор);
       КонецЦикла;
   КонецЕсли;
Конецпроцедуры

Процедура глПроверкаУдаленияПроведения(Регистратор, Отказ, НабЗаписей = Неопределено) Экспорт
Перем рег, стрСвертки;

   Если глПроводитьБезПроверки() Тогда
       Возврат;
   КонецЕсли;
   
   Если НабЗаписей <> Неопределено Тогда
       стрСвертки = НабЗаписей.стрИзмерения + ",НомерСтроки";
       ПроверкаУдаленияПроведения(НабЗаписей, Отказ, стрСвертки, Регистратор);
   Иначе
       Для Каждого рег Из Регистратор.Движения Цикл
           Попытка
               стрСвертки = рег.стрИзмерения + ",НомерСтроки";
           Исключение
               Продолжить;
           КонецПопытки;
           ПроверкаУдаленияПроведения(рег, Отказ, стрСвертки, Регистратор);
       КонецЦикла;
   КонецЕсли;
Конецпроцедуры

Процедура ПроверкаПроведения(НабЗаписей, Отказ, стрСвертки, Регистратор)
Перем тзНовДв, тзСтарДв;
   
   тзНовДв  = НабЗаписей.Выгрузить();
   тзстарДв = НабЗаписей.тзСтарыеДвижения;
   ПреобразоватьИСвернутьТаблицу(тзСтарДв, стрСвертки);
   ПреобразоватьИСвернутьТаблицу(тзНовДв, стрСвертки);
   ПодготовитьТаблицыДляАнализа(НабЗаписей.мИзмерения, тзСтарДв, тзНовДв);
   Если БудетПерерасход(НабЗаписей, тзСтарДв, Регистратор) Тогда
       Отказ = Истина;
   КонецЕсли;
   Если БудетПерерасход(НабЗаписей, тзНовДв, Регистратор) Тогда
       Отказ = Истина;
   КонецЕсли;

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

Процедура ПроверкаУдаленияПроведения(НабЗаписей, Отказ, стрСвертки, Регистратор)
Перем тзДв, стр;
   
   НабЗаписей.Прочитать();
   тзДв = НабЗаписей.Выгрузить();
   ПреобразоватьИСвернутьТаблицу(тзДв, стрСвертки, -1);
   Для Каждого Стр Из тзДв Цикл
       стр.НомерСтроки = ?(стр.Количество < 0, 1, 0);
   КонецЦикла;
   
   Если БудетПерерасход(НабЗаписей, тзДв, Регистратор) Тогда
       Отказ = Истина;
   КонецЕсли;
   
КонецПроцедуры

Процедура ПреобразоватьИСвернутьТаблицу(тзДв, стрСвертки, множитель = 1)
Перем стр;
   
   // Приведем все к виду движения Приход
   Для Каждого стр ИЗ тзДв Цикл
       стр.Количество = ?(стр.ВидДвижения = ВидДвиженияНакопления.Приход, стр.Количество, -стр.Количество) * множитель;
       стр.НомерСтроки = 0; // это поле будем использовать для свих нужд
   КонецЦикла;
   
   // Свернем по измерениям
   тзДв.Свернуть(стрСвертки, "Количество");
Конецпроцедуры

Процедура ПодготовитьТаблицыДляАнализа(мИзмерения, тзСтарыеДвижения, тзНовыеДвижения)
Перем стр, стрС, изм, двНайдено;    
   
   // При отсутствии старых движений проверять будем только расход
   Если тзСтарыеДвижения.Количество() = 0 Тогда
       Для Каждого стр ИЗ тзНовыеДвижения Цикл
           стр.НомерСтроки = ?(стр.Количество < 0, 1, 0) // метим строки с расходом
       КонецЦикла;
       Возврат;        
   КонецЕсли;
   
   // Более сложный случай - надо сопоставлять старые и новые движения
   Для Каждого стр ИЗ тзНовыеДвижения Цикл
       Если стр.Количество < 0 Тогда
           стр.НомерСтроки = 1;    // расход в любом случае надо контролировать
           Продолжить;        
       КонецЕсли;
       
       // приход надо сопоставить со старым приходом
       Для Каждого стрС ИЗ тзСтарыеДвижения Цикл
           Если стрС.НомерСтроки = 0 И стрС.Количество > 0 Тогда
               // должно быть совпадение по всем измерениям
               двНайдено = Истина;
               Для Каждого изм Из мИзмерения Цикл
                   Если стр[изм] = стрС[изм] Тогда
                       Продолжить;
                   Иначе
                       двНайдено = Ложь;
                       Прервать;
                   КонецЕсли;
               КонецЦикла;
               Если двНайдено Тогда
                   стрС.НомерСтроки = 2;        // выключаем из дальнейшего анализа
                   Если стр.Количество < стрС.Количество Тогда
                       стр.НомерСтроки = 1;    // новый приход меньше старого - надо контролировать
                   КонецЕсли;
               КонецЕсли;
           КонецЕсли;
       КонецЦикла;
   КонецЦикла;
   
   // второй проход по старым движениям - пометим приход
   Для Каждого стр ИЗ тзСтарыеДвижения Цикл
       Если стр.НомерСтроки = 0 И стр.Количество > 0 Тогда
           стр.НомерСтроки = 1;
           стр.Количество    = 0; // количество нам не нужно будет
       КонецЕсли
   КонецЦикла;
КонецПроцедуры

Функция БудетПерерасход(НабЗаписей, тзДвижения, Регистратор)
Перем стр, рез, отб, изм, регистр, СинонимРегистра;
   
   рез = Ложь;
   СинонимРегистра = НабЗаписей.Метаданные().Синоним;
   регистр = РегистрыНакопления[НабЗаписей.Метаданные().Имя];
   отб = Новый Структура(НабЗаписей.стрИзмерения);
   Для Каждого стр ИЗ тзДвижения Цикл
       Если стр.НомерСтроки = 1 Тогда
           Для Каждого изм ИЗ НабЗаписей.мИзмерения Цикл
               отб[изм] = стр[изм];
           КонецЦикла;
           кол = регистр.Остатки(, отб, НабЗаписей.стрИзмерения, "Количество");
           кол = ?(кол.Количество() = 0, 0, кол[0].Количество);
           
           кол = кол + стр.Количество;
           Если кол >= 0 Тогда
               Продолжить;
           КонецЕсли;        
           
           // будем выдавать стандартную диагностику, если в регистраторе не переопределена онная
           рез = Истина;
           Попытка
               Регистратор.Сообщить(набЗаписей, стр, кол);
           Исключение
               Сообщить("Перерасход остатка (" + Строка(кол) + ") по регистру '" + СинонимРегистра +"':", СтатусСообщения.Внимание);
               Для Каждого изм ИЗ НабЗаписей.мИзмерения Цикл
                   Если НЕ глПустаяИлиНеопределено(стр[изм]) Тогда
                       Сообщить("  - " + изм + ": " + Строка(стр[изм]));
                   КонецЕсли;
               КонецЦикла
           КонецПопытки;
       КонецЕсли;
   КонецЦикла;
   
   Возврат рез;

КонецФункции


ПроводитьБезПроверки = Ложь;



================= ЭТОТ КОД НАДО ВСТАВИТЬ В МОДУЛЬ НАБОРА ЗАПИСИ РЕГИСТРА НАКОПЛЕНИЯ ========
============ ПРОВЕРКА БУДЕТ ПРОИЗВОДИТСЯ ТОЛЬКО ПО РЕГИСТРАМ, В КОТОРЫЕ ДОБАВЛЕН ЭТОТ КОД ===

Перем тзСтарыеДвижения    Экспорт;            // Таблица значений для сохранения старых движений
Перем мИзмерения        Экспорт;    // Массив с именами измерений
Перем стрИзмерения        Экспорт;    // Имена измерений через запятую

Процедура ЗапомнитьСтарыеДвижения() Экспорт
   ЭтотОбъект.Прочитать();
   тзСтарыеДвижения = ЭтотОбъект.Выгрузить();
КонецПроцедуры;

// Инициализация переменных
стрИзмерения = "";
мИзмерения   = Новый Массив();
Для Каждого стр ИЗ ЭтотОбъект.Метаданные().Измерения Цикл
   мИзмерения.Добавить(стр.Имя);    
   стрИзмерения = стрИзмерения + "," + стр.Имя;
КонецЦикла;
стрИзмерения = Сред(стрИзмерения, 2);


Михей:
медленно и неэффективно. Контроль производится при соединении таблицы остатков и табличной части документа.

Nafanail:
1. На счет медленно - может быть, у кого проблемы с нормальным компьютером/сервером.
2. Неэффективно - что имеется ввиду? Проверка гарантировано не даст актульным остаткам в минус уйти - что еще может быть эффективней?
3. Табличная часть документа вообще никак не участвует. Михей! - будь внимательней....

Описание | Рубрикатор | Поиск | ТелепатБот | Захваченные статьи | Установки | Форум
© Станислав Митичкин (Волшебник), 2005-2025 | Mista.ru

Яндекс.Метрика