Сумма товарных позиций больше суммы оплат
Ситуация: в розничной сети иногда падают чеки. Текст ошибки в заголовке статьи. В логах при этом странное. После так называемого санкционированного закрытия формы сложной оплаты (это когда продавец все сделал, хочет уже чек и нажал мышкой кнопку Enter), вдруг перед фискализацией происходит повторное открытие этой же формы, как и положено стирается таблица оплат, поэтому суммы и не идут. Бились с этим месяца два, а причина оказалась потрясающей...
Дело в том, что код в современных типовых 1С-конфигурациях до сих пор устроен следующим образом:
// Чудовищная нелогичная мешанина процедур и функций
// с нелепыми названиями, например:
Процедура ЗавершитьОплатуПлатежнойКартойЧерезЭквайринговыйТерминалНачало(ПараметрДействия)
...
КонецПроцедуры
Процедура ЗавершитьОплатуПлатежнойКартойЧерезЭквайринговыйТерминалЗавершение(РезультатВыполнения, ПараметрДействия)
...
КонецПроцедуры
Процедура ЗавершитьОплатуПлатежнойКартойЧерезЭквайринговыйТерминалОкончание(ПараметрДействия)
...
КонецПроцедуры
// Ведь сразу же понятно что раньше: завершение или окончание :-)
// И все это ради callback hell и излишней декомпозиции.
// И вдруг, в одной из процедур на глубине стека вызовов примерно 15,
// когда разработчики окончательно запутались, встречается такое:
ПодключитьОбработчикОжидания("ЗавершитьОплатуТоваровПослеВыводаСдачи", 0.1, Истина);
// Ну то есть, через одну десятую секунды после завершения всего стека
// один раз будет вызвана новая процедура.Мягко говоря, странное решение. То же самое лучше делать по другому (через установку какого-нибудь флага):
Процедура ПроцессФискализации()
ПерваяФазаФискализацииУспешна = Ложь; //Реквизит формы
ПерваяФазаФискализации();
Если ПерваяФазаФискализацииУспешна Тогда
ВтораяФазаФискализации();
КонецЕсли;
КонецПроцедуры
// При этом весь callback hell был бы скрыт под капот этих двух процедур,
// а на глубине стека вызовов при успехе надо просто установить значение флага,
// так же логичнее и проще.Но вернемся. Минимальный интервал обработки ожидания как раз 0.1 секунды. И что же происходит в этот миг? Правильно, система ожидаемо ожидает событий. Вроде как успеть послать какой-нибудь сигнал очень маловероятно. Но все-таки, как оказалось, мисклик мышки (второе нажатие на ту же кнопку) случается. Повторно открывается форма, стирается таблица оплат. А потом, как ни в чем не бывало, обработчиком ожидания запускается процедура приводящая к фискализации и чек падает не пройдя форматно-логический контроль фискальных данных. При большом количестве чеков на 400 кассах всего таких событий по всей сети не более пяти в неделю. Ну то есть никаких чудес, банальное сомнительное решение при многочисленных повторах приводит к редким труднообнаружимым ошибкам.
...
ИдетПроцессФискализации = Истина; //Реквизит формы
ПодключитьОбработчикОжидания("ЗавершитьОплатуТоваровПослеВыводаСдачи", 0.1, Истина);
...
Процедура КартинкаЧО07СложнаяОплатаНажатие(Элемент, СтандартнаяОбработка)
Если ИдетПроцессФискализации Тогда
СтандартнаяОбработка = Ложь;
Возврат;
КонецЕсли;
...
КонецПроцедуры
Процедура ЗавершитьОплатуТоваровПослеВыводаСдачи()
ИдетПроцессФискализации = Ложь;
...
КонецПроцедурыP.S. Часто ловлю себя на мысли, что молодые 30-35-летние программисты и те кто только входят в профессию, считают, что callback hell (обратные вызовы из асинхронных методов в новые процедуры) - это нормальный стиль программирования. Хотя это самое вопиющее проявление говнокода. Ведь и в джаваскрипте, откуда эта дичь пошла, и в 1С давно реализованы обратные вызовы в те же процедуры с помощью async / await promise (асинх / ждать обещание).