Работа с кассой Atol через веб-сервер ДТО-10




Поддержка многопользовательской печати на одном устройстве ККТ. Поддержка изменений в законодательстве (Переход на ФФД 1.05 и НДС 20%).

Предыстория

Так сложилось что работа с кассой в среде 1С строиться через внешнюю компоненту.
Одна из проблем, с которой столкнулись мы – «зависание» com-порта для работы с кассой завершенным сеансом 1С при печати чеков на 1 ККМ с нескольких компьютеров. Также нам не нравилась установка драйвера ДТО на каждом компьютере, с которого требовалось обращение к кассе. 

Ранее использовали службу fdsvc на компьютере, где подключена касса, устанавливатли драйвер на каждом ПК для обеспечения общения с службой. Так же необходимо было зарегистрировать на каждом компьютере библиотеку драйвера Атол FprnM1C.dll, если же dll в новой версии драйвера с тем же именем, то предварительно необходимо почистить временные файлы. Создавался COM объект, в него передавались данные в зависимости от операции, при этом соединение с кассой держалось все время пока выполнялась операция, а так же код выполнялся в синхронном режиме, ожидая выполнения каждой операции.

Что сделали:

Компания Атол выпустила новую версию драйвера, которая поддерживает работу с кассой через HTTP-запросы к веб-серверу Атол. Существует публикация, реализующая механизм работы с этой версией драйвера, но она нас не устраивала закрытостью кода и мы решили реализовать свой механизм.

 

Порядок действий:

1.Установка и настройка сервера от Atol

Скачиваем последний ДТО 10 с сайта Atol

Для работы Web-сервера требуется установленная Java версии 1.8 и выше (х32).

В момент установки отмечаем, что необходимо установить Web-сервер (данное расширение присутствует только в 32-х битном драйвере)

После установки по адресу http://hostname:16732/settings производим настройки web-сервера

Настраиваем параметры подключения

Включаем web-сервер

После перезагрузки необходимо перезапустить службу

2. Взаимодействие с web-сервером

Для обращения к кассе нам необходимо хранить ip-адрес и порт по которому происходит взаимодействие с кассой

В справочнике кассы добавили реквизиты АдресВебСервера и ПортВебСервера

Алгоритм работы

Для добавления задания в очередь на выполнения необходимо отправить его POST-запросом на адрес http://hostname:16732/requests, указав его уникальный идентификатор. В ответ сервер вернет код результата в виде HTTP-статуса.

(Отправляем запрос на регистрацию задания в очереди печати, когда необходимо распечатать чек)

Для того, чтобы узнать результат задания, необходимо отправить GET-запрос на адрес http://hostname:16732/requests/. В ответ вернется JSON, содержащий в себе статусы задания и его результаты.

(Тут сложнее, необходимо запрашивать статусы задач, для этого мы должны организовать хранение отправленных заданий и проверять по ним ответы, мы организовали хранение через регистр сведений, ключом выступил Объект (любая ссылка) – так как необходимо было контролировать уникальность документов оплаты отправленных на печать, и обеспечить повторную отправку печати в случае неуспеха предыдущего задания)

Для отмены задания, которое еще не начало обрабатываться, необходимо отправить DELETE-запрос на адрес http://hostname:16732/requests/. Нельзя отменить задание, которое выполняется в данный момент.

 

Примеры кода

Для выполнения операции была реализована функция выполнения команды, в качестве входных параметров:

Касса на которой необходимо произвести печать

Операция выполняемая в данный момент

Дополнительные параметры для проведения определенной операции

Реализовали функция для постановки в очередь заданий

// Функция - Выполнить операцию

//

// Параметры:

//  Касса - Справочник.КассыККМ - касса на которой необходимо произвести операцию

//  Операция - Строка - Реализованы "ОтчетБезГашения", "ЗакрытиеСмены" и "ФискальныйЧек"

//  ДополнительныеПараметры - Структура - необходимые параметры для выполнения операций

//

// Возвращаемое значение:

//  Ответ - Строка - сообщение о результате выполнения операции

Функция ВыполнитьОперацию(Касса,Операция,ДополнительныеПараметры) Экспорт



HTTPЗапрос = Новый HTTPЗапрос();

HTTPЗапрос.АдресРесурса = "/requests";

HTTPЗапрос.Заголовки.Вставить("Content-Type", "application/json");



Если Операция = "ОтчетБезГашения" Тогда

СтруктураJSON = СформироватьСтруктуруДляОтчетаБезГашения(Касса,Операция,ДополнительныеПараметры);

ИначеЕсли Операция = "ЗакрытиеСмены" Тогда

СтруктураJSON = СформироватьСтруктуруДляЗакрытияСмены(Касса,Операция,ДополнительныеПараметры);

ИначеЕсли Операция = "ФискальныйЧек" Тогда

СтруктураJSON = СформироватьСтруктуруДляФискальногоЧека(Касса,Операция,ДополнительныеПараметры);

Иначе

Возврат "Ошибка выполнения операции! " + "Операция " + Операция + " не реализована!";

КонецЕсли;



Если СтруктураJSON = Неопределено Тогда

Возврат "Не удалось зарегистрировать в очередь!, ошибка формирования менеджера задания (JSON)";

КонецЕсли;



ЗаписьJSON = новый ЗаписьJSON;

ЗаписьJSON.УстановитьСтроку();

ЗаписатьJSON(ЗаписьJSON,СтруктураJSON);

СтрокаЗапросаJS = ЗаписьJSON.Закрыть();

HTTPЗапрос.УстановитьТелоИзСтроки(СтрокаЗапросаJS,КодировкаТекста.UTF8);



Попытка

Соединение = Новый HTTPСоединение(Касса.АдресВебСервера,Касса.ПортВебСервера);

ОтветHTTP = Соединение.ОтправитьДляОбработки(HTTPЗапрос);

Исключение

Возврат "Ошибка регистрации в очереди! Проверьте работоспособность сервера и параметры кассы!";

КонецПопытки;



//обработаем ответ



Тело = ОтветHTTP.ПолучитьТелоКакСтроку();

Если Не ОтветHTTP.КодСостояния = 201 Тогда

Возврат "Ошибка регистрации в очереди!";

КонецЕсли;



//добавим в регистр очереди

Если Операция = "ФискальныйЧек" Тогда

новМенеджер = РегистрыСведений.ОчередьРаботыСКкт.СоздатьМенеджерЗаписи();

новМенеджер.Период = ТекущаяДата();

новМенеджер.Объект = ДополнительныеПараметры.ДокументОплаты;

новМенеджер.Операция = Операция;

новМенеджер.уникИД = СтруктураJSON.uuid;

новМенеджер.Касса = ДополнительныеПараметры.ДокументОплаты.Касса;

новМенеджер.Записать(Ложь);

КонецЕсли;



Возврат "Данные добавлены в очередь!";

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

Для каждой операции необходимо формировать свое тело запроса, реализовали под каждую операцию свою функцию которая формирует структуру для отправки на задание

Функция СформироватьСтруктуруДляОтчетаБезГашения(Касса,Операция,ДополнительныеПараметры)



уникИД = Формат(ТекущаяДата(),"ДФ=ддММггггЧЧммсс");

СтруктураJSON = Новый Структура();

СтруктураJSON.Вставить("uuid",уникИД);

МассивПараметров = Новый Массив();

СтруктураОперация  = Новый Структура("type","reportX");

МассивПараметров.Добавить(СтруктураОперация);

СтруктураОператор = Новый Структура();

СтруктураОператор.Вставить("name",Строка(ПараметрыСеанса.Пользователь));

МассивПараметров.Добавить(СтруктураОператор);

СтруктураJSON.Вставить("request",МассивПараметров);



Возврат СтруктураJSON;

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



Функция СформироватьСтруктуруДляЗакрытияСмены(Касса,Операция,ДополнительныеПараметры)



уникИД = Формат(ТекущаяДата(),"ДФ=ддММггггЧЧммсс");

ЗаписьJSON = новый ЗаписьJSON;

ЗаписьJSON.УстановитьСтроку();

СтруктураJSON = Новый Структура();

СтруктураJSON.Вставить("uuid",уникИД);

МассивПараметров = Новый Массив();

СтруктураОперация  = Новый Структура("type","closeShift");

МассивПараметров.Добавить(СтруктураОперация);

СтруктураОператор = Новый Структура();

СтруктураОператор.Вставить("name",Строка(ПараметрыСеанса.Пользователь));

МассивПараметров.Добавить(СтруктураОператор);

СтруктураJSON.Вставить("request",МассивПараметров);



Возврат СтруктураJSON;



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



Функция СформироватьСтруктуруДляФискальногоЧека(Касса,Операция,ДополнительныеПараметры)



ДокументОплаты = ДополнительныеПараметры.ДокументОплаты;

Если Не ПустаяСтрока(ДокументОплаты.НомерЧека) Тогда



СообщениеПользователю = Новый СообщениеПользователю();

СообщениеПользователю.Текст = "По документу уже пробит чек!";

СообщениеПользователю.КлючДанных = ДокументОплаты;

СообщениеПользователю.Сообщить();

Возврат Неопределено;



КонецЕсли;



СтруктураОтвета = ПолучитьСостояниеЗадания("",ДокументОплаты.Касса,ДокументОплаты);

Если Не СтруктураОтвета.ВозможнаОтправка Тогда



СообщениеПользователю = Новый СообщениеПользователю();

СообщениеПользователю.Текст = СтруктураОтвета.ОписаниеОшибки;

СообщениеПользователю.КлючДанных = ДокументОплаты;

СообщениеПользователю.Сообщить();

Возврат Неопределено;



КонецЕсли;



уникИД = Формат(ДокументОплаты.Номер,"ЧГ=") + "-" + Формат(ТекущаяДата(),"ДФ=ддММггЧЧммсс");

ЗаписьJSON = новый ЗаписьJSON;

ЗаписьJSON.УстановитьСтроку();

СтруктураJSON = Новый Структура();

СтруктураJSON.Вставить("uuid",уникИД);



МассивПараметров = Новый Массив();

СтруктураЧека = Новый Структура();

//Тип задания

//sell - чек прихода buy - чек расхода sellReturn - чек возврата прихода buyReturn - чек возврата расхода

ВидОперации = ДокументОплаты.ВидОперации;

Если ВидОперации = ПредопределенноеЗначение("Перечисление.ВидыОперации.ОплатаКлиентом") ИЛИ

ВидОперации = ПредопределенноеЗначение("Перечисление.ВидыОперации.ПополнениеЛицевогоСчета") ИЛИ

ВидОперации = ПредопределенноеЗначение("Перечисление.ВидыОперации.ВнесениеДенежныхСредств") Тогда



type = "sell";



ИначеЕсли ВидОперации = ПредопределенноеЗначение("Перечисление.ВидыОперации.ВозвратКлиенту") Тогда



type = "sellReturn";



Иначе



Возврат Неопределено;



КонецЕсли;

СтруктураЧека.Вставить("type",type);

//Электронный чек

СтруктураЧека.Вставить("electronically",Ложь);

//useVAT18 использовать при регистрации чека ставку налога 18%

СтруктураЧека.Вставить("useVAT18",Ложь);

//taxationType Система налогообложения

Если Касса.СистемаНалогооблажения = ПредопределенноеЗначение("Справочник.СистемыНалогообложения.Общая") Тогда

taxationType = "osn";

ИначеЕсли Касса.СистемаНалогооблажения = ПредопределенноеЗначение("Справочник.СистемыНалогообложения.УпрощеннаяДоход") Тогда

taxationType = "usnIncome";

ИначеЕсли Касса.СистемаНалогооблажения = ПредопределенноеЗначение("Справочник.СистемыНалогообложения.УпрощеннаяДоходМинусРасход") Тогда

taxationType = "usnIncomeOutcome";

ИначеЕсли Касса.СистемаНалогооблажения = ПредопределенноеЗначение("Справочник.СистемыНалогообложения.ЕНВД") Тогда

taxationType = "envd";

ИначеЕсли Касса.СистемаНалогооблажения = ПредопределенноеЗначение("Справочник.СистемыНалогообложения.ЕСН") Тогда

taxationType = "esn";

ИначеЕсли Касса.СистемаНалогооблажения = ПредопределенноеЗначение("Справочник.СистемыНалогообложения.ПСН") Тогда

taxationType = "patent";

Иначе

taxationType = "";

КонецЕсли;

Если Не ПустаяСтрока(taxationType) Тогда

СтруктураЧека.Вставить("taxationType",taxationType);

КонецЕсли;

//данные о кассире

СтруктураОператор = Новый Структура();

СтруктураОператор.Вставить("name", Строка(ДокументОплаты.Автор));

СтруктураЧека.Вставить("operator",СтруктураОператор);



//данные о товарах массив items

МассивПозиций = Новый Массив();



Если ВидОперации = Перечисления.ВидыОперации.ПополнениеЛицевогоСчета ИЛИ

ВидОперации = ПредопределенноеЗначение("Перечисление.ВидыОперации.ВнесениеДенежныхСредств") Тогда



СтруктураПозиции = Новый Структура();

СтруктураПозиции.Вставить("type","position");

СтруктураПозиции.Вставить("name","Оказание медицинских услуг по договору");

СтруктураПозиции.Вставить("price",ДокументОплаты.Сумма);

СтруктураПозиции.Вставить("quantity",1);

СтруктураПозиции.Вставить("amount",ДокументОплаты.Сумма);

СтруктураПозиции.Вставить("paymentMethod","fullPrepayment");

СтруктураПозиции.Вставить("paymentObject","service");

СтруктураНДС = Новый Структура();

СтруктураНДС.Вставить("type","none");

СтруктураПозиции.Вставить("tax",СтруктураНДС);

МассивПозиций.Добавить(СтруктураПозиции);



Иначе



СписокУслуг = ДокументОплаты.СписокУслуг.Выгрузить();

СуммаОплтыБонусами = 0;

МассивОплатБонусами = ДокументОплаты.ВидыОплат.НайтиСтроки(Новый Структура("ВидОплаты",ПредопределенноеЗначение("Перечисление.ВидОплаты.Бонусами")));

Если МассивОплатБонусами.Количество() = 1 Тогда

СуммаОплтыБонусами = МассивОплатБонусами[0].Сумма;

КонецЕсли;



Для Каждого СтрПозиций Из СписокУслуг Цикл



Если СуммаОплтыБонусами = 0 Тогда

Прервать;

КонецЕсли;



Если СтрПозиций.Сумма - 1 <= СуммаОплтыБонусами Тогда

СуммаОплтыБонусами = СуммаОплтыБонусами - (СтрПозиций.Сумма - 1);

СтрПозиций.Сумма = 1;

СтрПозиций.Цена = СтрПозиций.Сумма/СтрПозиций.Количество;

Иначе

СтрПозиций.Сумма = СтрПозиций.Сумма - СуммаОплтыБонусами;

СтрПозиций.Цена = СтрПозиций.Сумма/СтрПозиций.Количество;

СуммаОплтыБонусами = 0;

КонецЕсли;



КонецЦикла;



Для Каждого СтрОплаты Из СписокУслуг Цикл



СтруктураПозиции = Новый Структура();

СтруктураПозиции.Вставить("type","position");

СтруктураПозиции.Вставить("name",СтрОплаты.Услуга.Наименование);

СтруктураПозиции.Вставить("price",СтрОплаты.Цена);

СтруктураПозиции.Вставить("quantity",СтрОплаты.Количество);

СтруктураПозиции.Вставить("amount",СтрОплаты.Сумма);

Если СтруктураЧека.type = "sell" Тогда

СтруктураПозиции.Вставить("infoDiscountAmount",СтрОплаты.СуммаБезСкидки - СтрОплаты.Сумма);

КонецЕсли;

//paymentMethod - Признак способа рaсчета

//fullPrepayment - предоплата 100%

//prepayment - предоплата

//advance - аванс

//fullPayment - полный расчет

//partialPayment - частичный расчет и кредит

//credit - передача в кредит

//creditPayment - оплата кредита

СтруктураПозиции.Вставить("paymentMethod","fullPrepayment");



//paymentObject

//commodity - товар

//excise - подакцизный товар

//job - работа

//service - услуга

//gamblingBet - ставка азартной игры

//gamblingPrize - выигрыш азартной игры

//lottery - лотерейный билет

//lotteryPrize - выигрыш лотереи

//intellectualActivity - предоставление результатов интерелектуальной деятельности

//payment - платеж

//agentCommission - агентское вознаграждение

//proprietaryLaw - имущественное право

//nonOperatingIncome - внереализационный доход

//insuranceСontributions - страховые взносы

//merchantTax - торговый сбор

//resortFee - курортный сбор

//composite - составной предмет расчета

//another - иной предмет расчета

СтруктураПозиции.Вставить("paymentObject","service");



//tax

СтруктураНДС = Новый Структура();

СтруктураНДС.Вставить("type","none");

СтруктураПозиции.Вставить("tax",СтруктураНДС);



МассивПозиций.Добавить(СтруктураПозиции);



КонецЦикла;



КонецЕсли;

СтруктураЧека.Вставить("items", МассивПозиций);



//формирование стуктуры оплат

МассивОплат = Новый Массив();

Для Каждого СтрОплаты Из ДокументОплаты.ВидыОплат Цикл

СтруктураОплаты = Новый Структура();

Если СтрОплаты.ВидОплаты = ПредопределенноеЗначение("Перечисление.ВидОплаты.БезНаличными") Тогда

СтруктураОплаты.Вставить("type", "electronically");

ИначеЕсли СтрОплаты.ВидОплаты = ПредопределенноеЗначение("Перечисление.ВидОплаты.Наличными") Тогда

СтруктураОплаты.Вставить("type", "cash");

Иначе

Продолжить;

КонецЕсли;

СтруктураОплаты.Вставить("sum", СтрОплаты.Сумма);

МассивОплат.Добавить(СтруктураОплаты);

КонецЦикла;

СтруктураЧека.Вставить("payments",МассивОплат);



СтруктураJSON.Вставить("request",СтруктураЧека);



Возврат СтруктураJSON;

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

 

Опрос заданий организовали через фоновое задание раз в 60 секунд

Процедура ОбработкаОчередиККТ() Экспорт

Запрос = Новый Запрос();

Запрос.Текст = "ВЫБРАТЬ

| ОчередьРаботыСККТ.Период КАК Период,

| ОчередьРаботыСККТ.Объект КАК Объект,

| ОчередьРаботыСККТ.Операция КАК Операция,

| ОчередьРаботыСККТ.уникИД КАК уникИД,

| ОчередьРаботыСККТ.Касса КАК Касса,

| ОчередьРаботыСККТ.Статус КАК Статус,

| ОчередьРаботыСККТ.Результат КАК Результат

|ИЗ

| РегистрСведений.ОчередьРаботыСККТ КАК ОчередьРаботыСККТ

|ГДЕ

| ОчередьРаботыСККТ.Статус = """"";



Рез = Запрос.Выполнить().Выбрать();



Пока Рез.Следующий() Цикл

Если Не ЗначениеЗаполнено(Рез.Объект) Тогда

МенеджерЗаписи = РегистрыСведений.ОчередьРаботыСКкт.СоздатьМенеджерЗаписи();

МенеджерЗаписи.Объект = Рез.Объект;

МенеджерЗаписи.Период = Рез.Период;

МенеджерЗаписи.Прочитать();

МенеджерЗаписи.Удалить();

Продолжить;

КонецЕсли;



СтруктураОтвета = ПолучитьСостояниеЗадания(Рез.уникИД,Рез.Объект.Касса,Рез.Объект);

Если Не СтруктураОтвета.Результат Тогда

Продолжить;

КонецЕсли;



СтатусОперации = СтруктураОтвета.results[0].status;

ОписаниеОшибки = СтруктураОтвета.results[0].errorDescription;

Если СтатусОперации = "error" Тогда

//запишем ошибку

МенеджерЗаписи = РегистрыСведений.ОчередьРаботыСКкт.СоздатьМенеджерЗаписи();

МенеджерЗаписи.Объект = Рез.Объект;

МенеджерЗаписи.Период = Рез.Период;

МенеджерЗаписи.Прочитать();

МенеджерЗаписи.Статус = СтатусОперации;

МенеджерЗаписи.Результат = ОписаниеОшибки;

МенеджерЗаписи.Записать();

Продолжить;

ИначеЕсли СтатусОперации = "ready" Тогда



//проверим номер чека и присвоем оплате

НомерЧека = СтруктураОтвета.results[0].result.fiscalParams.fiscalDocumentNumber;

ДокОплаты = Рез.Объект.ПолучитьОбъект();

ДокОплаты.НомерЧека = НомерЧека;

Попытка

ДокОплаты.Записать(РежимЗаписиДокумента.Запись);

Исключение

Продолжить;

КонецПопытки;

//если задание выполненно успешно то данные о задании удаляем из очереди

МенеджерЗаписи = РегистрыСведений.ОчередьРаботыСКкт.СоздатьМенеджерЗаписи();

МенеджерЗаписи.Объект = Рез.Объект;

МенеджерЗаписи.Период = Рез.Период;

МенеджерЗаписи.Прочитать();

МенеджерЗаписи.Удалить();

КонецЕсли;

КонецЦикла;



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

Функция ПолучитьСостояниеЗадания(УникИД, Касса, Объект) Экспорт



Если Не ЗначениеЗаполнено(УникИД) Тогда

//найдем последний УникИД по объекту

Запрос = Новый Запрос();

Запрос.Текст = "ВЫБРАТЬ

| ОчередьРаботыСККТСрезПоследних.Объект КАК Объект,

| ОчередьРаботыСККТСрезПоследних.Операция КАК Операция,

| ОчередьРаботыСККТСрезПоследних.уникИД КАК уникИД,

| ОчередьРаботыСККТСрезПоследних.Касса КАК Касса

|ИЗ

| РегистрСведений.ОчередьРаботыСККТ.СрезПоследних КАК ОчередьРаботыСККТСрезПоследних

|ГДЕ

| ОчередьРаботыСККТСрезПоследних.Объект = &Объект";

Запрос.УстановитьПараметр("Объект",Объект);

Рез = Запрос.Выполнить().Выбрать();

Если Рез.Следующий() Тогда

УникИД = Рез.уникИД;

Иначе

СтруктураОтвета = Новый Структура();

СтруктураОтвета.Вставить("Результат",Ложь);

СтруктураОтвета.Вставить("ВозможнаОтправка",Истина);

СтруктураОтвета.Вставить("ОписаниеОшибки","Объект не отправлялся в очередь для печати!");

Возврат СтруктураОтвета;

КонецЕсли;



КонецЕсли;



Запрос = Новый Запрос();



HTTPСоединение = Новый HTTPСоединение(Касса.АдресВебСервера,Касса.ПортВебСервера,,,,,);

HTTPЗапрос = Новый HTTPЗапрос("/requests/" + УникИД);

Попытка

Ответ = HTTPСоединение.Получить(HTTPЗапрос);

ОписаниеОшибки = Ответ.ПолучитьТелоКакСтроку();

Исключение

Возврат Неопределено;

КонецПопытки;



Попытка

ЧтениеJSON = Новый ЧтениеJSON();

ЧтениеJSON.УстановитьСтроку(ОписаниеОшибки);

СтруктураОтвета = ПрочитатьJSON(ЧтениеJSON);

ЧтениеJSON.Закрыть();

Исключение

СтруктураОтвета = Новый Структура();

СтруктураОтвета.Вставить("Результат",Ложь);

СтруктураОтвета.Вставить("ВозможнаОтправка",Ложь);

СтруктураОтвета.Вставить("ОписаниеОшибки","Не удалось прочитать статус");

Возврат СтруктураОтвета;

КонецПопытки;

СтатусОперации = СтруктураОтвета.results[0].status;

СтруктураОтвета.Вставить("Результат",Истина);

СтруктураОтвета.Вставить("ВозможнаОтправка",?(СтатусОперации = "error",Истина,Ложь));

СтруктураОтвета.Вставить("ОписаниеОшибки",?(СтатусОперации = "ready","Чек успешно расепчатан ранее!",СтруктураОтвета.results[0].errorDescription));

Возврат СтруктураОтвета;



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



Документация по взаимодействию с web-сервером atol описана на сайте http://integration.atol.ru/#web-server

Спасибо за внимание, будем рады если статья будет полезна Вам, готовы ответить на вопросы в комментариях.

36 Comments

  1. CheBurator
    Для отмены задания, которое еще не начало обрабатываться, необходимо отправить DELETE-запрос на адрес http://hostname:16732/requests/. Нельзя отменить задание, которое выполняется в данный момент.

    а)- имеем задание, которое еще не отмечено как обработанное.

    б)- посылаем DELETE

    в)-между а и б) — задание уже выполнилось —

    г) что получим в ответ? какие действия отрабатываются?

    д) какие действия отрабатываются если DELETE попадает на «запрос выполняется в данный момент»..?

    е) разница в отработке и реакции системы кассира на Г и Д — есть?

    Reply
  2. ix5s

    (1)

    по методу Delete

    Отмена задания

    HTTP запрос

    DELETE http://<hostname&gt;:16732/requests/<uuid>

    Возвращаемые ошибки

    В случае успешного чтения результата сервер вернет статус 200 (OK) и JSON с результатом.

    Также могут вернуться следующие ошибки:

    404 (Not Found) — задание с заданным uuid не найдено

    405 (Method Not Allowed) — задание с заданным uuid нельзя отменить (уже выполнено или выполняется)

    г- ответ получим 405, если задание выполняется в данный момент, т.е. в режиме inProgress.

    в данной реализации нет механизма обрабатывающего данный метод, так как очередь большая не собирается, задание очень быстро переходят в статус inProgress

    д — действия не выполняются

    е — для кассира разницы нет

    как механизм можно предлагать совершить возврат по операции, если уже был отправлен чек, если же успешно отменили то можно делать возврат без пробития чека

    Надеюсь ответил на ваш вопрос

    Reply
  3. karpik666

    Идея с очередью задания интересна, но требует доработки конфигурации, зачем разрабатывать свой механизм, если у фирмы 1с есть свой, описанный в требования к разработке драйверов. Также непонятно зачем передавать идентификатор в виде текущей даты, если «Новый УникальныйИдентификатор» с этим прекрасно справляется?

    У себя ранее уже сделал разработку для обычных форм https://infostart.ru/public/956348/ , которая использует типовой функционал от фирмы 1С, и прекрасно отправляет и получает информацию о напечатанных чеках, также использую его и в своей платной разработке.

    Reply
  4. 3vs

    Может быть кому-то будет интересно, я свои Атол 25Ф подключал к программе 1С Отель, работающий в терминале посредством преобразователя порта USR-TCP232-302.

    Прикладываю описание этого способа.

    Но мы используем на каждом рабочем месте свой фискальник, т.к. на сайте 1С где-то была информация, что вообще-то печатать на одном фискальнике с нескольких рабочих мест является грехом по версии налоговой, мы стараемся не грешить! 🙂

    Reply
  5. ix5s

    (3) Идентификатор подойдет любой, это правда

    по поводу механизма через драйвер, причина в обращении к ККТ одновременно с нескольких ПК.

    Первая проблема что периодически происходит зависание ком порта, не все время, но данная ошибка появлялась неоднократно.

    Вторая проблема что если одновременно послать на печать чек то одному из двух вернется ошибка недоступности кассы

    Третья долгая печать чека, которая на 30ф например, и ожидать печати чека хотя бы с 10 позициями, не очень то и хочется, а через com вся программа зависает и ждет ответа

    p.s. свой механизм именно для решения данных проблем и исключения множественного кода

    а как вы ожидаете ответ от веб-сервера?

    Reply
  6. karpik666

    (5) я не предлагаю отказаться от веб-сервера, и обращаться напрямую через com объект. Отправка http запросов логичный выбор. Просто ожидать ответа от веб-сервера можно по таймауту, без очереди заданий, опрашивая нужное задание на выполнение, для такого варианта нужно будет предусмотреть, чтобы результат запроса не кэшировался.

    Reply
  7. medangel

    (4) Информацию по «греховности печати» не видели, но получали множество запросов от «бизнеса» на реализацию данного функционала.

    Reply
  8. medangel

    (6) Согласны, что ваш вариант тоже «хорош». Но для себя выбрали асинхронность.

    Reply
  9. karpik666

    (8) такой вариант как у вас хорош для интернет магазина, когда не требуется сию секундный ответ от фискального регистратора, и можно поместить задание в очередь и ожидать печати долгого времени.

    Reply
  10. 3vs

    Нашёл текст на сайте 1С:

    https://its.1c.ru/db/kkt/content/77/hdoc

    Можно ли использовать одну онлайн-кассу на нескольких торговых точках, расположенных по одному адресу?

    Рекомендации для осторожных налогоплательщиков

    На каждом торговом месте должна быть отдельная онлайн-ККТ. Одна ККТ не может одновременно использоваться на нескольких торговых местах.

    По общему правилу ККТ применяется в обязательном порядке всеми организациями и индивидуальными предпринимателями при осуществлении расчетов, включая расчеты при осуществлении торговли (п. 1 ст. 1.2 Федерального закона от 22.05.2003 № 54-ФЗ, Закон № 54-ФЗ).

    Торговое место – это место, используемое для совершения сделок розничной купли-продажи с/без использованием(я) торгового объекта, находящееся под управлением одного лица (ГОСТ Р 51303-2013. Национальный стандарт Российской Федерации. Торговля. Термины и определения, утв. приказом Росстандарта от 28.08.2013 № 582-ст).

    Поэтому использовать одну ККТ при торговле на нескольких торговых местах, расположенных по одному адресу, нельзя.

    (См. письмо УФНС по г. Москве от 02.03.2011 № 17-26/019349@).

    Рекомендации для тех, кто готов отстаивать свою позицию

    Одна ККТ может одновременно использоваться на нескольких торговых местах, находящихся по одному адресу.

    Дело в том, что при регистрации ККТ необходимо указать адрес и место установки (применения) ККТ (п. 2 ст. 4.2 Закона № 54-ФЗ). При этом положения Закона № 54-ФЗ не запрещают использовать ККТ при торговле на нескольких торговых местах, расположенных по одному адресу. Значит, это правомерно.

    Это следует из буквального толкования норм законодательства.

    Отметим, что данную позицию, скорее всего, придется отстаивать в суде.

    Reply
  11. medangel

    «Торговое место» в нашем случае это 1 reception (стойка администратора-регистратора), где обслуживается клиент. Наше субъективное мнение — закон не нарушается, проверки налоговых органов наши клиенты проходили успешно в прошлом году.

    Спасибо за ссылку — будем предоставлять клиентам перед выбором схемы работы. Наша МИС «МедАнгел» поддерживает обе схемы.

    Reply
  12. medangel

    (9) Не совсем — ожидать практически не приходится, работа через очередь практически не проигрывает по скорости «работе в лоб».

    Reply
  13. androgin

    локально работает. Но не понимаю, почему удаленно не подключается касса )))

    хост и порт указал, в роутере порт перенаправил на нужный сервер….

    а не подключается))

    Reply
  14. medangel

    (13) а файервол включен?

    если да то порт надо в исключения прописать

    Reply
  15. androgin

    (14) да, забыл про него)))

    ну и добавил/доработал ваши методы, кстати, обнаружил ошибку в составлении json. Кассир не определяется вашим способом.

    Функция СформироватьСтруктуруДляОткрытияЗакрытияСмены(Касса, Операция, ДополнительныеПараметры)
    
    Если Операция = «ОткрытиеСмены» Тогда
    ТипОперации = «openShift»;
    ИначеЕсли Операция = «ЗакрытиеСмены» Тогда
    ТипОперации = «closeShift»;
    Иначе
    Возврат Неопределено;
    КонецЕсли;
    
    УИД = Формат(ТекущаяДата(),»ДФ=ддММггггЧЧммсс»);
    
    СтруктураОператор = Новый Структура(«name», Строка(ПараметрыСеанса.ТекущийПользователь));
    
    СтруктураЧека = Новый Структура;
    СтруктураЧека.Вставить(«type», ТипОперации);
    СтруктураЧека.Вставить(«operator», СтруктураОператор);
    
    МассивПараметров = Новый Массив;
    МассивПараметров.Добавить(СтруктураЧека);
    
    СтруктураJSON = Новый Структура();
    СтруктураJSON.Вставить(«uuid», УИД);
    СтруктураJSON.Вставить(«request», МассивПараметров);
    
    Возврат СтруктураJSON;
    
    КонецФункции
    

    Показать

    Reply
  16. androgin

    (14) и еще вопрос: ККТ не возвращает ответы)

    Даже о состоянии ккт.

    Возможно ли потому, что ККТ не фискализирована?

    Reply
  17. androgin

    (14) С ответами разобрался (сам дурак)))))

    Reply
  18. pma

    Коллеги, никто не делал метод СформироватьСтруктуру для ЧекаКоррекции?

    Reply
  19. tacu

    А кто-нибудь ставил для себя логическо-философскую задачу по закрытию смен в 1С и в ККТ:

    если закрытие смены в 1С увязано с закрытием смены в ККТ (как это делается в типовых конфигурациях) то при работе нескольких юзеров с одной кассой мы получим конфликт, когда в 1С смена открыта, а в ККТ — закрыта предыдущим пользователем

    ?

    и какие способы решения (с логическими обоснованиями, ессэсно, (и для пользователей тоже)) назовёт уважаемое сообщество?

    Reply
  20. medangel

    (18) У нас пока нет данной реализации.

    Reply
  21. medangel

    (19) Не совсем понял — как влияет работа «нескольких юзеров»? При закрытии смены — смена должна закрываться и в 1С, и в ККТ (вне зависимости от количества/режима работы пользователей).

    Reply
  22. tacu

    (21) ну как… первый юзер закрывает свою смену и смену в ККТ, второй юзер не может закрыть смену в ККТ, тк она уже, и свою смену может не закрыть, если конфигурация к этому критична

    Reply
  23. pma

    Коллеги, а как Вы обрабатываете ситуацию когда очередь заблокировалась?

    У меня она разблокировалась только после физического отключения кассы.

    Перезапуск сервера не помогал.

    Reply
  24. medangel

    (23)Добрый день, такая ситуация не возникала, можно получать статус последнего задания и на основании него понимать состояние очереди. А ошибка какая была? или просто потерялся конект? один раз так было, был занят ком порт другой программой

    Reply
  25. pma

    Настроен тестовый ККТ с тестовым ФН

    Послал на Сервер 462 чека.

    На почту пришло 3 чека от тестового ОФД.

    Дальше тишина

    Запросил состояние очереди

    2019-03-04 15:28:02.541 INFO [JsonTaskServlet] GET /stat/requests

    2019-03-04 15:28:02.545 INFO [JsonTaskServlet] 200 {«not_ready_count»:441,»block_request_uuid»:»00000001667-040319150552″,»is_blocked»:true,»ready_count»:31,»canceled_count»:0}

    2019-03-04 15:28:07.665 WARN [DriverWorker] Не удалось восстановить состояние, продолжаем попытки…

    2019-03-04 15:28:08.665 INFO [DriverWorker] Обнаружена блокировка очереди задачей ‘00000001667-040319150552’

    Больше ничего не менялось.

    По заданиям кроме заблокированного возвращался статус wait

    2019-03-04 15:11:20.225 INFO [JsonTaskServlet] GET /requests/00000004120-040319150645

    2019-03-04 15:11:20.225 INFO [JsonTaskServlet] 200 {«results»:[{«result»:null,»errorDescription»:»»,»errorCode»:0,»status»:»wait»}]}

    2019-03-04 15:11:20.265 INFO [JsonTaskServlet] GET /requests/00000001667-040319150552

    2019-03-04 15:11:20.265 INFO [JsonTaskServlet] 200 {«results»:[{«result»:null,»errorDescription»:»Нет связи»,»errorCode»:2,»status»:»blocked»}]}

    2019-03-04 15:11:20.287 INFO [JsonTaskServlet] GET /requests/00000001668-040319150552

    Остановка/Запуск сервера ККТ ни на что ни влиял

    После физического выключения/ включения ККТ

    2019-03-04 15:58:15.577 INFO [JsonTaskServlet] GET /stat/requests

    2019-03-04 15:58:15.583 INFO [JsonTaskServlet] 200 {«not_ready_count»:442,»block_request_uuid»:»00000001667-040319150552″,»is_blocked»:true,»ready_count»:31,»canceled_count»:0}

    2019-03-04 15:59:38.417 INFO [DriverWorker] Обнаружена блокировка очереди задачей ‘00000001667-040319150552’

    2019-03-04 15:59:38.602 INFO [DriverWorker] Соединение восстановленно, задача ‘00000001667-040319150552’ не выполнена

    2019-03-04 15:59:38.614 INFO [DriverWorker] Обработка задачи ‘00000001667-040319150552’ завершена, разблокируем очередь

    И дальше вся очередь обработалась

    Reply
  26. pma

    Мне просто надо с 01/07/2019 кучу чеков пробивать и посылать клиентам — вот и провожу нагрузочное тестирование и пытаюсь понять какое и сколько мне нужно оборудования и как все это заставить работать.

    Reply
  27. Solikamsk

    (26) Не подскажите какие «Настроен тестовый ККТ с тестовым ФН » используете?

    Reply
  28. medangel

    у нас для тестирования релизов используются ККТ:

    Retail 02F(Штрих-ФР-02Ф) и Атол 55Ф

    Тестовый ФН:

    МГМ-ФН

    Reply
  29. Solikamsk

    (28) Спасибо, что отвечаете. У меня на любые запросы ответ:

    <ht ml>

    <head>

    <met a http-equiv=»Content-Type» content=»text/html;charset=utf-8″/>

    <title>Error 500 Server Error</title>

    </head>

    <body><h2>HTTP ERROR 500</h2>

    <p>Problem accessing /requests. Reason:

    <pre> Server Error</pre></p><h3>Caused by:</h3><pre>java.lang.StringIndexOutOfBoundsException: String index out of range: 0

    at java.lang.String.charAt(Unknown Source)

    at ru.atol.drivers10.webserver.Utils.readFromReader(Utils.java:24)

    ….

    Подскажите, что не так?

    Reply
  30. Solikamsk

    А вот пытаюсь postman

    Reply
  31. Solikamsk

    Вопрос с «HTTP ERROR 500» и с postman закрыт. У меня тупая ошибка была.

    Reply
  32. Junix

    Кто-нибудь сталкивался с такой ошибкой?

    Ошибка парсинга запроса (поле «items[0].price» отсутствует)

    Кусок json

    «items»: [
    {
    «type»: «position»,
    «name»: «CD диск принято от Медицинские услуги»,
    «price»: 1,
    «quantity»: 1,
    «amount»: 1,
    «tax»: {
    «type»: «none»,
    «sum»: 0
    }
    }
    ]

    Показать

    Reply
  33. ix5s

    (32)

    price

    нет ли случайно проблемы с раскладкой клавиатуры? так вроде все хорошо

    Reply
  34. Junix

    (33) неа. И сам писал и на всякий случай с инета копировал, и с калькулятора вводил…

    Reply
  35. Junix

    (33)

    Полный текст json

    {
    «uuid»: «1abf1c07-8867-4e40-b69d-243d5e68b68716092019095213»,
    «request»: {
    «type»: «sell»,
    «ignoreNonFiscalPrintErrors»: false,
    «electronically»: false,
    «useVAT18»: false,
    «taxationType»: «osn»,
    «paymentsPlace»: «Организация»,
    «operator»: {
    «name»: «Кассир»,
    «vatin»: «ИНН»
    },
    «items»: [
    {
    «type»: «position»,
    «name»: «CD диск принято от Медицинские услуги»,
    «price»: 1,
    «quantity»: 1,
    «amount»: 1,
    «tax»: {
    «type»: «none»,
    «sum»: 0
    }
    }
    ],
    «payments»: [
    {
    «type»: «other»,
    «sum»: 1
    }
    ]
    }
    }

    Показать

    Reply
  36. Junix

    (35)

    Решение было простое. ИНН кассира не надо указывать

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *