Даже сегодня, несмотря на доминирование современных версий платформы 1С, множество стабильно работающих предприятий продолжают использовать проверенную временем конфигурацию «1С:Торговля и Склад 7.7» (ТиС). Архитектурно она спроектирована очень логично и лаконично, однако в ее типовом коде затаились критические недоработки («дыры»), которые при определенных обстоятельствах могут приводить к незаметному, но серьезному искажению управленческого, складского и партионного учета.
В этой статье мы подробно разберем пять ключевых технических проблем «1С:Торговля и Склад 7.7», выясним физические причины их возникновения и внедрим готовые программные исправления, которые защитят базу данных от лавинообразных ошибок.
---
Проанализируем работу глобальной процедуры глДвижениеОборотовПродаж, которая отвечает за формирование записей в регистре оборотного типа Продажи. Эта процедура вызывается практически из всех документов, влияющих на оборот реализации (например, «РеализацияТМЦ», «ОтчетККМ», «ВозвратОтПокупателя» и другие).
В чем заключается техническая уязвимость? В платформе «1С:Предприятие 7.7» объект регистра, переданный через контекст (РегПродажи = Конт.Reg.Продажи), хранит свое состояние в памяти на протяжении всего цикла проведения документа. Метод ДвижениеВыполнить() производит запись текущего набора измерений и ресурсов в базу данных, но физически не очищает значения этих реквизитов в памяти объекта регистра.
Если в процессе проведения одного документа (например, сводного «Отчета ККМ») процедура глДвижениеОборотовПродаж вызывается последовательно в цикле для разных строк, где в одной строке идет чистая продажа (количество >= 0), а в другой — возврат (количество < 0), то происходит логический сбой. При переходе выполнения на ветку возврата (Иначе), в регистр корректно запишутся ресурсы возврата (СебестоимостьВ, ПродСтоимостьВ, КоличествоВ). Однако ресурсы обычной продажи (Себестоимость, ПродСтоимость, Количество), которые были заполнены на предыдущем шаге цикла, не обнулятся и «прицепом» запишутся в это же движение возврата. В результате аналитика продаж по компании безвозвратно искажается.
Рассмотрим правильное решение этой проблемы. Нам необходимо принудительно обнулять противоположные ресурсы регистра в обеих ветках условия Если. Откроем Глобальный модуль и найдем процедуру глДвижениеОборотовПродаж. Внесем изменения, отмеченные маркерами нетиповых доработок:
Процедура глДвижениеОборотовПродаж(Конт,
Фирма,
Контрагент,
Поставщик,
Номенклатура,
Количество,
Себестоимость,
ПродСтоимость) Экспорт
РегПродажи = Конт.Регистр.Продажи;
// Заполняем измерения регистра
РегПродажи.Фирма = Фирма;
РегПродажи.Поставщик = Поставщик;
РегПродажи.Покупатель = Контрагент;
РегПродажи.Номенклатура = Номенклатура;
// Заполняем ресурсы
Если Количество >= 0 Тогда
РегПродажи.Себестоимость = Себестоимость;
РегПродажи.ПродСтоимость = ПродСтоимость;
РегПродажи.Количество = Количество;
// ДОБАВЛЕНО НЕТИПОВОЕ: Обнуляем ресурсы возврата для исключения наложения данных
РегПродажи.СебестоимостьВ = 0;
РегПродажи.ПродСтоимостьВ = 0;
РегПродажи.КоличествоВ = 0;
// КОНЕЦ ДОБАВЛЕНИЯ
Иначе
РегПродажи.СебестоимостьВ = - Себестоимость;
РегПродажи.ПродСтоимостьВ = - ПродСтоимость;
РегПродажи.КоличествоВ = - Количество;
// ДОБАВЛЕНО НЕТИПОВОЕ: Обнуляем ресурсы прямой продажи при фиксации возврата
РегПродажи.Себестоимость = 0;
РегПродажи.ПродСтоимость = 0;
РегПродажи.Количество = 0;
// КОНЕЦ ДОБАВЛЕНИЯ
КонецЕсли;
РегПродажи.DvizhenieVypolnit();
КонецПроцедуры //глДвижениеОборотовПродаж()
Благодаря этому простому дополнению мы гарантируем, что при любой последовательности итераций в циклах проведения данные продаж и возвратов никогда не перемешаются.
---
Разберем методологическую особенность списания партий при оформлении документа «ВозвратОтПокупателя» в регистре ПартииНаличие. В типовой конфигурации при проведении возврата система определяет исходную партию и выполняет ее оприходование (возврат на баланс складов).
Посмотрим на типовой фрагмент кода, отвечающий за заполнение измерений партии:
Если (МетодРасчетаСебестоимости <> Перечисление.МетодыРасчетаСебестоимости.FIFO)
и (МетодРасчетаСебестоимости <> Перечисление.МетодыРасчетаСебестоимости.LIFO)
и (РегПартии.СтатусПартии <> глСП.Т_Принятый) Тогда
РегПартии.Партия = ПолучитьПустоеЗначение("Справочник.Партии");
РегПартии.ДатаПартии = "";
Иначе
РегПартии.Партия = ТаблПартий.Партия;
РегПартии.ДатаПартии = ТекДок.ДатаДок; // !!! Обратите внимание на эту строку !!!
КонецЕсли;
Почему разработчики «1С» приняли такое решение? Запись возвращаемого товара датой документа возврата (ТекДок.ДатаДок) защищает систему от нарушения хронологической последовательности. Если бы возвращенная партия приходовалась датой своего первоначального поступления (которая была в прошлом), то при восстановлении последовательности документов задним числом этот товар мог бы автоматически списаться документами продаж, которые физически произошли до того, как клиент фактически вернул товар. Это вызвало бы логическое противоречие в учете.
Минусы типового подхода: Подобная схема полностью ломает логику партионного учета ФИФО. Старый возвращенный товар (который может быть значительно дешевле или дороже текущих остатков) становится в конец очереди списания как «самый свежий».
Если для вашего бизнеса критично отслеживать реальную себестоимость и вы не практикуете проведение документов продаж «задним числом» между датой продажи и датой возврата, вы можете скорректировать эту строку, подставив оригинальную дату партии:
// Вместо: РегПартии.ДатаПартии = ТекДок.ДатаДок;
РегПартии.ДатаПартии = ТаблПартий.ДатаПартии; // Установка оригинальной даты партии
Важно помнить: Внедрение этой правки потребует от операторов строгого соблюдения хронологии и исключения проведения документов реализации задним числом.
---
Выясним причину, по которой отчет «Ведомость по остаткам ТМЦ» может выводить некорректные («красные») отрицательные остатки, если в настройках отчета отключена группировка по складам.
В процедуре ВывестиГруппировку() отчета заложена оптимизация для внутренних перемещений со склада на склад. Разработчики логично рассудили: если товар просто переместился между складами одной организации, а детализация по складам в отчете выключена, то нет смысла выводить строки документа ПеремещениеТМЦ, ведь общий остаток по фирме не изменился.
Посмотрим на типовой код оптимизации:
Если НазваниеГруппировки = "Документ" Тогда
НазваниеСекции="Документ";
// Внутренние перемещения выводятся только в том случае, если установлена детализация или фильтр по складу
Если (Запрос.Документ.Vid() = "ПеремещениеТМЦ") и (НомерГруппировкиПоСкладу = 9999) и (ВыбСклад.Выбран() = 0) Тогда
Продолжить;
КонецЕсли;
Где кроется логический баг? В «1С:Торговле и Складе 7.7» документ ПеремещениеТМЦ выполняет двойную функцию: он может перемещать товары не только со склада на склад, но и с фирмы на фирму (например, переток товаров между юридическими лицами одного холдинга).
Если происходит перемещение между разными фирмами, то это движение не является внутренним для конкретного юрлица! Игнорируя этот документ при выводе отчета без группировки по складам, система «забывает» учесть списание остатков у фирмы-отправителя и приход у фирмы-получателя. Это рождает ложную красноту в отчетах.
Проанализируем и внедрим корректное решение. Нам нужно отсекать перемещение документов только в том случае, если фирма-получатель совпадает с фирмой-отправителем:
Если НазваниеГруппировки = "Документ" Тогда
НазваниеСекции="Документ";
// Внутренние перемещения выводятся только в том случае, если установлена детализация или фильтр по складу
Если (Запрос.Документ.Вид() = "ПеремещениеТМЦ") и (НомерГруппировкиПоСкладу = 9999) и (ВыбСклад.Выбран() = 0) Тогда
// ДОБАВЛЕНО НЕТИПОВОЕ: Проверяем, действительно ли это перемещение внутри одной фирмы
Если Запрос.Документ.ФирмаПолучатель = Запрос.Документ.Фирма Тогда
Продолжить; // Отсекаем только действительно внутренние перемещения
КонецЕсли;
// КОНЕЦ ДОБАВЛЕНИЯ
КонецЕсли;
После этой доработки отчет будет работать абсолютно адекватно при любых настройках группировок.
---
При ведении учета в реальных условиях (особенно при интенсивной работе и регулярных корректировках документов задним числом) в регистрах резервов могут возникать ошибочные отрицательные остатки.
Рассмотрим, как рассчитывается свободный остаток товара на складах и фирмах в глобальной процедуре глСписаниеОстатковТМЦ():
СвободныйОстаток = ОстатокНаСкладе - РезервНаСкладе;
Если в результате программных сбоев или ошибок операторов значение РезервНаСкладе ушло в минус (например, стало равно -5), то математически мы получаем следующую картину: 10 - (-5) = 15.
Система начинает считать, что свободного товара на складе больше, чем есть физически на остатках! Это позволяет проводить новые расходные документы на несуществующий товар, порождая лавинообразный рост отрицательных остатков на складах.
Для предотвращения этой проблемы применим защитный механизм — ограничим вычитаемый резерв снизу нулем с помощью функции Макс(). Найдем в процедуре глСписаниеОстатковТМЦ() строки расчета и заменим их:
// Было в типовой:
// Если (ОстатокНаСкладе - РезервНаСкладе) < ТаблНоменклатуры.Количество Тогда
// Если (ОстатокПоФирме - РезервПоФирме) < ТаблНоменклатуры.Количество Тогда
// ДОБАВЛЕНО НЕТИПОВОЕ: Защита от отрицательного резерва
Если (ОстатокНаСкладе - Макс(0, РезервНаСкладе)) < ТаблНоменклатуры.Количество Тогда
...
Если (ОстатокПоФирме - Макс(0, РезервПоФирме)) < ТаблНоменклатуры.Количество Тогда
// КОНЕЦ ДОБАВЛЕНИЯ
Теперь даже при наличии ошибок в базе и уходе резервов «в минус» система не позволит списать физически отсутствующий на складе товар.
---
В модуле проведения документа «ПеремещениеТМЦ» при перемещении товара на розничный склад система обращается к табличной части для извлечения розничной цены:
ТаблицаДокумента.ПолучитьСтрокуПоНомеру(ТаблСписПартий.НомерСтрокиДокумента);
Регистр.ПартииНаличие.ЦенаПрод = ТаблицаДокумента.ЦенаПродПриход;
В чем опасность? Метод ПолучитьСтрокуПоНомеру() осуществляет позиционирование по физическому индексу (номеру) строки в объекте ТаблицаЗначений.
Если в процессе проведения документа (особенно при наличии сторонних доработок, интеграций или обработки услуг) таблица документа была отсортирована, свернута или из нее были удалены какие-либо строки, то физический индекс строки в таблице перестает соответствовать значению реквизита НомерСтрокиДокумента. В результате система либо выдаст ошибку времени выполнения (выход за границы таблицы), либо получит цену от совершенно другого товара.
Чтобы избежать этой уязвимости, выполним безопасный поиск строки по значению колонки идентификатора:
Если СкладПолучатель.РозничныйСклад = 0 Тогда
Регистр.ПартииНаличие.ЦенаПрод = 0;
Иначе
// Для определения продажной цены безопасно найдем строку документа по ее исходному номеру
// ДОБАВЛЕНО НЕТИПОВОЕ
Поз = 0;
Если ТаблицаДокумента.НайтиЗначение(ТаблСписПартий.НомерСтрокиДокумента, Поз, "НомерСтрокиДокумента") = 1 Тогда
ТаблицаДокумента.ПолучитьСтрокуПоНомеру(Поз);
Регистр.ПартииНаличие.ЦенаПрод = ТаблицаДокумента.ЦенаПродПриход;
Иначе
Регистр.ПартииНаличие.ЦенаПрод = 0; // На всякий случай, если строка не найдена
КонецЕсли;
// КОНЕЦ ДОБАВЛЕНИЯ
КонецЕсли;
---
В завершение рассмотрим два неочевидных системных факта, знание которых сэкономит много времени при программировании на платформе «1С:Предприятие 7.7».
Выгрузить() в переменную, которая инициализирована как пустая строка, платформа может повести себя неожиданно:
ТЗнабор = СоздатьОбъект("ТаблицаЗначений");
ТЗнабор.НоваяКолонка("Номенклатура", "Справочник.Номенклатура");
ТЗнабор.НоваяКолонка("Количество", "Число");
ТЗКопия = "";
ТЗнабор.Выгрузить(ТЗКопия); // Переменная ТЗКопия останется обычным типом "Строка"!
ТаблицаЗначений с аналогичной структурой колонок, всегда предварительно инициализируйте целевую переменную:
ТЗКопия = СоздатьОбъект("ТаблицаЗначений");
ТЗнабор.Выгрузить(ТЗКопия); // Теперь тип переменной строго "ТаблицаЗначений"
глАдмин1С.АктивныеПодключения() используется для мониторинга сеансов пользователей. Помните: чтобы этот метод успешно выполнялся и не вызывал аварийного завершения работы программы (Runtime Error), у учетной записи пользователя 1С, под которой запускается этот код, в Конфигураторе обязательно должно быть установлено разрешение «Использование в качестве Ole Automation сервера = ДА».
Применение этих несложных правил и исправлений позволит значительно повысить отказоустойчивость вашей базы данных и обеспечит гарантированную точность отчетов и проведения документов в системе «1С:Торговля и Склад 7.7».