June 27

Сумма товарных позиций больше суммы оплат

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

←23 | заметка 24 | 25→