Собственная реализация функционала печати чеков по 54-ФЗ из 1С Розница 2.2
Собственно причиной сподвигнувшей меня на написание данного «велосипеда» послужило то, для того чтобы распечатался полноценный чек из 1С, не используюя РМК, нужно очень много телодвижений от менеджера. А именно: создать документ реализации, на основании его создать документ «Чек» или «ПКО». Если печатать чек прямо из документа реализации, то в чеке выходят не понятные надписи «Кредит».
РМК внедрить в моих условиях не удалось, т.к. менеджеры по продажам курсируют между различными ПК, заходя на каждом из них под своей учеткой, А в штатном РМК нет возможности выбрать на какой из принтеров печатать чек. А может и есть, но я покрутив его день, так и не смог настроить полноценную работу, чтобы при переходе с ПК на ПК, печатало на разные ККМ.
В итоге было решено разработать собственную методику печати чеков:
- Менеджер в документе «Реализация товаров и услуг» (или «Возврат») нажимает кнопку «Печатать чек»
- 1С записывает в базу данных MySQL данные о чеке, с указанием на какую ККМ его выводить (на основании имени ПК, на котором создавался документ)
- На сервере UBUNTU крутится скрипт, который смотрит новые чеки в базе MySQL и если таковые находит — печатает их.
Всё достаточно просто.
Обработчик 1С в общем модуле:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
Процедура ЗаписатьВБазуMySQLДокумент(документсс,имякомпа) Экспорт касса=1; Если имякомпа="serverrdp" тогда касса=2;конецесли; //касса Анна/Алёна Если имякомпа="ANNA" тогда касса=2;конецесли; //касса Анна/Алёна Если имякомпа="manager" тогда касса=2;конецесли; //касса Анна/Алёна Если имякомпа="SergCach" тогда касса=1;конецесли; //касса Дуров документ=документсс; если документ.Проведен=Ложь тогда сообщить("Документ не проведен, печатать чек не буду!"); возврат; конецесли; ИмяODBC = "sieufhleriufs"; ИмяБазы = "erf8se9r8f"; ИмяПользователя = "serifjsoeir"; Пароль = "serkfherj"; Connection = Новый COMОбъект("ADODB.Connection"); СтрокаПодключения = "DRIVER={MySQL ODBC 8.0 Unicode Driver};DATABASE=" + ИмяБазы + ";PWD=" + Пароль + ";PORT=3306;SERVER=" + ИмяODBC + ";UID=" + ИмяПользователя + ";"; Connection.Open(СокрЛП(СтрокаПодключения)); ВидЧека=1; // Возврат //сообщить(документ.Ссылка.Метаданные().Имя); если документ.Ссылка.Метаданные().Имя="РеализацияТоваров" тогда ВидЧека=0;конецесли; ТипОплаты=0; //наличными //проверяем, а есть ли связанный документ эквайринга? Запрос = Новый Запрос; Запрос.Текст = "ВЫБРАТЬ | ОплатаОтПокупателяПлатежнойКартойРасшифровкаПлатежа.ДокументРасчетовСКонтрагентом КАК ДокументРасчетовСКонтрагентом |ИЗ | Документ.ОплатаОтПокупателяПлатежнойКартой.РасшифровкаПлатежа КАК ОплатаОтПокупателяПлатежнойКартойРасшифровкаПлатежа |ГДЕ | ОплатаОтПокупателяПлатежнойКартойРасшифровкаПлатежа.ДокументРасчетовСКонтрагентом.Ссылка = &Ссылка" ; Запрос.УстановитьПараметр("Ссылка", документ.Ссылка); РезультатЗапроса = Запрос.Выполнить(); ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать(); Пока ВыборкаДетальныеЗаписи.Следующий() Цикл ТипОплаты=1; //электронно конеццикла; продавец=документ.Продавец.Наименование; инн=документ.Продавец.инн; для каждого стр из документ.Товары цикл ном=стр.номенклатура.Наименование; цена=Формат(стр.цена,"ЧГ="); сумма=Формат(стр.сумма,"ЧГ="); сумма=СтрЗаменить(сумма,",","."); цена=СтрЗаменить(цена,",","."); количество=Формат(стр.количество,"ЧГ="); //type_pay - способ расчета (электронно, налом) Запрос="insert into checks_1c (id,docnum,dt,goods,cost,seller,inn,kassa,type_pay,type_sell,result,cnt,summ) values (null,'"+Документ.Номер+"',now(),'"+ном+"',"+цена+",'"+продавец+"','"+инн+"',"+касса+","+ТипОплаты+","+ВидЧека+",0,"+количество+","+сумма+")"; //Сообщить(Запрос); РезультатЗапроса = Connection.Execute(Запрос); конеццикла; сообщить("Есть вероятность что чек сейчас распечатается ("+имякомпа+")..."); конецпроцедуры |
Обработчик в документах Реализации и Возврата:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
&НаСервере Процедура ПробитьЧекНаСервереГрибов(документ,имякомпа) ОбщийМодульГрибов.ЗаписатьВБазуMySQLДокумент(документ,имякомпа); КонецПроцедуры &НаКлиенте Процедура ПробитьЧек(Команда) //грибов. переделка печати чеков ддк=Объект; ПробитьЧекНаСервереГрибов(ддк,ИмяКомпьютера()); //Если Объект.ПробитЧек Тогда // ТекстСообщения = НСтр("ru = 'Чек уже пробит на фискальном регистраторе!'"); // ОбщегоНазначенияКлиентСервер.СообщитьПользователю(ТекстСообщения); // Возврат; //КонецЕсли; // //ОбработчикОповещения = Новый ОписаниеОповещения("ОповещениеВопросПроведениеПередПечатьюЧека", ЭтотОбъект); // //Если ФинансыКлиент.ПроверитьВозможностьПечатиЧека(ОбработчикОповещения, ЭтотОбъект) Тогда // НапечататьЧекКлиент(); //КонецЕсли; КонецПроцедуры |
Скрипт PHP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
#!/usr/bin/php <?php /* * (с) 2018 Грибов Павел * http://грибовы.рф * * Если исходный код найден в сети - значит лицензия GPL v.3 * * Сей дивный скрипт листает очередь не фискализированных платежей, и понемножку фискализирует... */ $debug=true; /////////// include_once ("sql.php"); $fl = fopen("/tmp/sales_list_1c.lock", "w"); if( ! ( $fl && flock( $fl, LOCK_EX | LOCK_NB ) ) ) { die("--копия скрипта уже запущена!"); }; function PutLog($txt){ global $debug; $dt=Date("d-m-Y H:i:s"); if ($debug==true){echo $dt." | "."$txt\n";}; }; $lb=new Tsql(); $lb->connect("жшыоа укшща","ыщазук","зцушказщуыш","щукшыащу","-connect to base"); while (true) { //ищу не проведенный номер документа $sql="select * from checks_1c where result=0 group by docnum"; $result = $lb->ExecuteSQL($sql,'--ищу не фискализированные продажи с группировкой по номеру документа') or die("Ошибка (1)!".mysqli_error($lb->idsqlconnection)); while($row = mysqli_fetch_array($result)) { $docnum=$row["docnum"]; //собираю чек $check=array(); $check["summ"]=0; $check["seller"]=$row["seller"]; $check["inn"]=$row["inn"]; $check["kassa"]=$row["kassa"]; $check["dt"]=$row["dt"]; $check["docnum"]=$row["docnum"]; $check["type_sell"]=$row["type_sell"]; //0 - реализация, 1 - возврат $check["type_pay"]=$row["type_pay"]; //0 - нал, 1 - электронно $check["goods"]=array(); $sql="select * from checks_1c where docnum='$docnum' and result=0"; $result2 = $lb->ExecuteSQL($sql,'--собираю чек') or die("Ошибка (2)!".mysqli_error($lb->idsqlconnection)); while($row2 = mysqli_fetch_array($result2)) { $good=array(); $good["name"]=$row2["goods"]; $good["cost"]=$row2["cost"]; $good["summ"]=$row2["summ"]; $good["cnt"]=$row2["cnt"]; $check["summ"]=$check["summ"]+$row2["summ"]; $check["goods"][]=$good; }; var_dump($check); PutLog("-----пробую фискализировать и распечатать чек"); $jsonparam= base64_encode(json_encode($check)); $com="/home/pavel/online_kassa/sale_1c.py --sale $jsonparam"; PutLog($com); $output=array(); $res=exec($com,$output,$ret); foreach ($output as $value) {echo "$value \n";}; if ($ret==99){ PutLog("----Хорошо! Фискализация удачная.. Меняю статус чека в базе НОС"); $sql="update checks_1c set result=1 where docnum='$docnum'"; $result3 = $lb->ExecuteSQL($sql,'--Меняю статус чека в базе НОС') or die("Ошибка (3)!".mysqli_error($lb->idsqlconnection)); } else { PutLog("--Всё плохо...."); }; }; PutLog("--поспим секундочку.."); sleep(5); // die(); }; |
Скрипт Python:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
#!/usr/bin/python3.5 # -*- coding: utf-8 -*- import base64 import ctypes import datetime import json from lib import IFptr import os import platform from pprint import pprint import sys #fptr = IFptr("") #get extr param if len(sys.argv) == 1: print ("Возможные параметры:") print ("--sale base64 - запрос продажи/возврата") print ("--z f@i@o inn [num] - формирование Z-отчета и закрытие смены для кассира на номере кассы") exit(0) arg = sys.argv[1] if arg=="--z": print ("Формируем Z-отчет и закрываем смену!") fio=sys.argv[2].replace("@"," ") inn=sys.argv[3] kassa=sys.argv[4] ip = "192.168.0.104" #касса по умолчанию if kassa == "2": ip = "192.168.0.104"; #алена if kassa == "1": ip = "192.168.0.103"; #дуров print("--будем печатать Z-Отчет на " + ip); print ("ФИО:",fio) print ("ИНН:",inn) print ("Z-report") #connect to driver LIBRARY_PATH = os.path.dirname(os.path.abspath(__file__)) fptr = IFptr(os.path.join(LIBRARY_PATH, "libfptr10.so")) #connect to ATOL fptr.setSingleSetting(IFptr.LIBFPTR_SETTING_MODEL, str(IFptr.LIBFPTR_MODEL_ATOL_AUTO)) fptr.setSingleSetting(IFptr.LIBFPTR_SETTING_PORT, str(IFptr.LIBFPTR_PORT_TCPIP)) fptr.setSingleSetting(IFptr.LIBFPTR_SETTING_IPADDRESS, ip) fptr.setSingleSetting(IFptr.LIBFPTR_SETTING_IPPORT, "5555") fptr.setSingleSetting(IFptr.LIBFPTR_SETTING_ACCESS_PASSWORD, "0") fptr.setSingleSetting(IFptr.LIBFPTR_SETTING_USER_PASSWORD, "30") fptr.applySingleSettings() #открываем соединение fptr.open() isOpened = fptr.isOpened() if isOpened == 0: print ("Не удалось открыть соединение с ККМ!") exit(1) fptr.setParam(1021,fio) fptr.setParam(1203, inn) fptr.operatorLogin() # Отчет о закрытии смены fptr.setParam(IFptr.LIBFPTR_PARAM_REPORT_TYPE, IFptr.LIBFPTR_RT_CLOSE_SHIFT) fptr.report() if arg == "--sale": bs64 = sys.argv[2]; bs = base64.b64decode(bs64.encode('ascii')).decode("utf-8"); print(bs.encode("utf-8")) param = json.loads(bs) ip = "192.168.0.104" #касса по умолчанию #ip = "192.168.0.227" #касса по умолчанию if param["kassa"] == "2": ip = "192.168.0.104"; #алена if param["kassa"] == "1": ip = "192.168.0.103"; #дуров print("--будем печатать на " + ip); #connect to driver LIBRARY_PATH = os.path.dirname(os.path.abspath(__file__)) fptr = IFptr(os.path.join(LIBRARY_PATH, "libfptr10.so")) #connect to ATOL fptr.setSingleSetting(IFptr.LIBFPTR_SETTING_MODEL, str(IFptr.LIBFPTR_MODEL_ATOL_AUTO)) fptr.setSingleSetting(IFptr.LIBFPTR_SETTING_PORT, str(IFptr.LIBFPTR_PORT_TCPIP)) fptr.setSingleSetting(IFptr.LIBFPTR_SETTING_IPADDRESS, ip) fptr.setSingleSetting(IFptr.LIBFPTR_SETTING_IPPORT, "5555") fptr.setSingleSetting(IFptr.LIBFPTR_SETTING_ACCESS_PASSWORD, "0") fptr.setSingleSetting(IFptr.LIBFPTR_SETTING_USER_PASSWORD, "30") fptr.applySingleSettings() #открываем соединение fptr.open() isOpened = fptr.isOpened() if isOpened == 0: print ("Не удалось открыть соединение с ККМ!") exit(1) seller=param["seller"]; inn=param["inn"] fptr.setParam(1021,seller) fptr.setParam(1203, inn) fptr.operatorLogin() print("Открываем чек"); if param["type_sell"]=="0": # продажа print("-продажа"); fptr.setParam(IFptr.LIBFPTR_PARAM_RECEIPT_TYPE, IFptr.LIBFPTR_RT_SELL) else: print("-возврат"); fptr.setParam(IFptr.LIBFPTR_PARAM_RECEIPT_TYPE, IFptr.LIBFPTR_RT_SELL_RETURN) fptr.setParam(IFptr.LIBFPTR_PARAM_RECEIPT_ELECTRONICALLY, False) fptr.setParam(1008, "office@ts35.ru") rz=fptr.openReceipt() if rz< 0: print("{} [{}]".format(fptr.errorCode(), fptr.errorDescription())) print("Регистрируем позиции"); for goods in param["goods"]: nm=goods["name"] cost=goods["cost"] summ=goods["summ"] cnt=goods["cnt"] print (nm,cost,summ,cnt); fptr.setParam(IFptr.LIBFPTR_PARAM_COMMODITY_NAME,nm) fptr.setParam(IFptr.LIBFPTR_PARAM_PRICE, float(summ)/float(cnt)) fptr.setParam(IFptr.LIBFPTR_PARAM_QUANTITY, cnt) fptr.setParam(IFptr.LIBFPTR_PARAM_TAX_TYPE, IFptr.LIBFPTR_TAX_NO) fptr.setParam(1212, 1) # товар fptr.setParam(1214, 4) # полный расчет fptr.registration() print("Регистрируем итог"); # Регистрация итога (отрасываем копейки) fptr.setParam(IFptr.LIBFPTR_PARAM_SUM, param["summ"]) rz=fptr.receiptTotal() if rz < 0: print("{} [{}]".format(fptr.errorCode(), fptr.errorDescription())) print("Выбираем способ расчета"); if param["type_pay"]=="0": print("-нал"); fptr.setParam(IFptr.LIBFPTR_PARAM_PAYMENT_TYPE, IFptr.LIBFPTR_PT_CASH) else: print("-карта"); fptr.setParam(IFptr.LIBFPTR_PARAM_PAYMENT_TYPE, IFptr.LIBFPTR_PT_ELECTRONICALLY) fptr.setParam(IFptr.LIBFPTR_PARAM_PAYMENT_SUM, param["summ"]) rz=fptr.payment() if rz < 0: print("{} [{}]".format(fptr.errorCode(), fptr.errorDescription())) print("Закрываем чек"); rz=fptr.closeReceipt() if rz < 0: print("{} [{}]".format(fptr.errorCode(), fptr.errorDescription())) if fptr.checkDocumentClosed() < 0 or rz<0: print ("Документ не закрылся!") del fptr exit(1) else: print ("Вроде чек распечатали..") del fptr exit(99) fptr.close() del fptr |
Добрый день.
Подскажите, пожалуйста, а как при таком подходе получить данные о чеке — номер, ФПД, дату и время чека?
В данном случае в 1С обратно не затаскивается эта информация, а пишется в базу MySQL. Теоретически если нужно, можно и в 1С затащить, но нужды не возникло.