Сумма товарных позиций больше суммы оплат
Ситуация: в розничной сети иногда падают чеки. Текст ошибки в заголовке статьи. В логах при этом странное. После так называемого санкционированного закрытия формы сложной оплаты (это когда продавец все сделал, хочет уже чек и нажал мышкой кнопку 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 (асинх / ждать обещание).