diff --git a/kkm_server_plus/.DS_Store b/kkm_server_plus/.DS_Store new file mode 100644 index 0000000..27a8133 Binary files /dev/null and b/kkm_server_plus/.DS_Store differ diff --git a/kkm_server_plus/__init__.py b/kkm_server_plus/__init__.py new file mode 100644 index 0000000..511a0ca --- /dev/null +++ b/kkm_server_plus/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import controllers +from . import models \ No newline at end of file diff --git a/kkm_server_plus/__manifest__.py b/kkm_server_plus/__manifest__.py new file mode 100644 index 0000000..4716e0e --- /dev/null +++ b/kkm_server_plus/__manifest__.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +{ + 'name': "KKM Server Plus+", + "license": "LGPL-3", + 'summary': """ + Интеграция с онлайн кассами через ККМСервер""", + + 'description': """ + Интеграция с онлайн кассами через ККМСервер, проверка маркировки Честный Знак + """, + + 'author': "MK.Lab, devrave", + 'website': "https://inf-centre.ru", + + 'category': 'Uncategorized', + 'version': '16.2024', + + 'depends': ['point_of_sale', 'pos_lot_selection'], + + 'installable': True, + + 'data': [ + 'security/ir.model.access.csv', + 'views/pos.xml', + 'static/src/xml/PosIndex.xml', + ], + + 'assets': { + 'point_of_sale.assets': [ + 'kkm_server_plus/static/src/js/UniversalIDUpdate.js', + #'kkm_server_plus/static/src/js/UniversalIDUpdate.xml', + 'kkm_server_plus/static/src/js/PosModel.js', + 'kkm_server_plus/static/src/js/open.js', + 'kkm_server_plus/static/src/js/close.js', + 'kkm_server_plus/static/src/js/ReportX.js', + 'kkm_server_plus/static/src/js/CleanGUID.js', + 'kkm_server_plus/static/src/xml/service_charge.xml', + 'kkm_server_plus/static/src/js/All_js.js', + 'kkm_server_plus/static/src/js/Cash.js', + 'kkm_server_plus/static/src/js/PayByPaymentCard.js', + 'kkm_server_plus/static/src/js/CancelPaymentByPaymentCard.js', + 'kkm_server_plus/static/src/js/ReturnPaymentByPaymentCard.js', + 'kkm_server_plus/static/src/js/Pay.js', + 'kkm_server_plus/static/src/js/ClosePop.js', + 'kkm_server_plus/static/src/js/TerminalReport.js', + 'kkm_server_plus/static/src/xml/ClosePop.xml', + 'kkm_server_plus/static/src/xml/PayByPaymentCard.xml', + 'kkm_server_plus/static/src/js/CheckKM.js', + ], + }, + + 'qweb': [ + 'static/src/xml/*.xml', + ], + +} diff --git a/kkm_server_plus/controllers/__init__.py b/kkm_server_plus/controllers/__init__.py new file mode 100644 index 0000000..457bae2 --- /dev/null +++ b/kkm_server_plus/controllers/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import controllers \ No newline at end of file diff --git a/kkm_server_plus/controllers/__pycache__/__init__.cpython-310.pyc b/kkm_server_plus/controllers/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..fa7a68d Binary files /dev/null and b/kkm_server_plus/controllers/__pycache__/__init__.cpython-310.pyc differ diff --git a/kkm_server_plus/controllers/__pycache__/controllers.cpython-310.pyc b/kkm_server_plus/controllers/__pycache__/controllers.cpython-310.pyc new file mode 100644 index 0000000..322ffae Binary files /dev/null and b/kkm_server_plus/controllers/__pycache__/controllers.cpython-310.pyc differ diff --git a/kkm_server_plus/controllers/controllers.py b/kkm_server_plus/controllers/controllers.py new file mode 100644 index 0000000..28e5129 --- /dev/null +++ b/kkm_server_plus/controllers/controllers.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +from odoo import http +from odoo.http import request +import logging +import math +from datetime import date,datetime,timedelta +import requests +import werkzeug +import json + +_logger = logging.getLogger(__name__) + +class OrderInfoController(http.Controller): + @http.route('/order/get_order_info', methods=['POST'], type='json', auth='public') + def get_order_info(self, order_id, **kwargs): + if order_id: + order = request.env['pos.order'].sudo().browse(order_id) + if order: + return {'state': 'success', + 'name': order.pos_reference, + 'UniversalID': order.UniversalID, + 'pay_kkm_guid': order.pay_kkm_guid, + 'pay_cancel_kkm_guid': order.pay_cancel_kkm_guid, + 'pay_return_kkm_guid': order.pay_return_kkm_guid, + } + else: + return {'state': 'error', 'message': 'Не удалось найти возврат.'} + else: + return {'state': 'error', 'message': 'Отсутствует идентификатор возврата.'} + +class LoggerJSMessage(http.Controller): + @http.route('/loggerjs/message', methods=['POST'], type='json', auth='public') + def get_order_info(self, Level, Message, **kwargs): + if Level=='warning': + _logger.warning(Message) + elif Level=='info': + #_logger.info(Message) + _logger.warning(Message) + elif Level=='error': + _logger.error(Message) + else: + _logger.warning(Message) + return Message + +class CashierInfo(http.Controller): + @http.route('/cashier/info', methods=['POST'], type='json', auth='public') + def get_order_info(self, cashier_id, **kwargs): + res = {} + if cashier_id: + cashier = request.env['res.users'].sudo().browse(cashier_id) + if cashier: + res = { + 'name': cashier.name, + 'inn':cashier.inn + } + return res \ No newline at end of file diff --git a/kkm_server_plus/models/__init__.py b/kkm_server_plus/models/__init__.py new file mode 100644 index 0000000..cc89676 --- /dev/null +++ b/kkm_server_plus/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import pos diff --git a/kkm_server_plus/models/barcode.py b/kkm_server_plus/models/barcode.py new file mode 100644 index 0000000..38571d0 --- /dev/null +++ b/kkm_server_plus/models/barcode.py @@ -0,0 +1,15 @@ +import json + +from odoo import fields, models + +class ProductTemplate(models.Model): + _inherit = 'product.template' + + barcode_json = fields.Char( + "Barcode", readonly=True, compute="_compute_barcode_json" + ) + + def _compute_barcode_json(self): + for b in self: + barcode_json = [x for x in b.mapped('product.barcode') if x] + b.barcode_json = json.dumps(barcode_json) \ No newline at end of file diff --git a/kkm_server_plus/models/pos.py b/kkm_server_plus/models/pos.py new file mode 100644 index 0000000..9d75539 --- /dev/null +++ b/kkm_server_plus/models/pos.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +from odoo import api, fields, models +import base64, requests, json + +class User(models.Model): + _inherit = 'res.users' + + inn = fields.Char(string='ИНН') + +class PosOrderNew(models.Model): + _inherit = 'pos.order' + + UniversalID = fields.Char(string='Банковский идентификатор транзакции', readonly=True) + UniversalIDRefund = fields.Char(string='Банковский идентификатор транзакции связанной продажи (для возврата)', readonly=True) + pay_kkm_guid = fields.Char(string='GUID платежа по банковской карте', readonly=True) + pay_cancel_kkm_guid = fields.Char(string='GUID отмены платежа по банковской карте', readonly=True) + pay_return_kkm_guid = fields.Char(string='GUID возврата платежа по банковской карте', readonly=True) + + def _order_fields(self, ui_order): + result = super()._order_fields(ui_order) + result['UniversalID'] = ui_order.get('UniversalID') + result['UniversalIDRefund'] = ui_order.get('UniversalIDRefund') + result['pay_kkm_guid'] = ui_order.get('pay_kkm_guid') + result['pay_cancel_kkm_guid'] = ui_order.get('pay_cancel_kkm_guid') + result['pay_return_kkm_guid'] = ui_order.get('pay_return_kkm_guid') + return result + +class PosConfigNew(models.Model): + _inherit = 'pos.config' + + enable_kkm_server = fields.Boolean(string='KKM Server') + UrlServer = fields.Char(string='UrlServer', default="http://localhost:5893") + User = fields.Char(string='Логин', default="Admin") + Password = fields.Char(string='Пароль', default="1111") + InnKkm_fiscal = fields.Char(string='ИНН фискального регистратора') + InnKkm_term = fields.Char(string='ИНН эквайрингового терминала') + NumDevice_fiscal = fields.Integer(string='Номер фискального регистратора', default=0) + NumDevice_term = fields.Integer(string='Номер эквайрингового терминала', default=0) + NotPrint = fields.Boolean(string='Не печатать на бумаге', default=False) + IsBarCode = fields.Boolean(string='Печатaть штрих-код', default=False) + XRep = fields.Boolean(string='Печатать Х-отчет', default=True) + DoubleReceipt = fields.Boolean(string='Печатать два чека', default=False) + ZRep = fields.Boolean(string='Печатать Z-отчет', default=True) + CashierName = fields.Many2one('res.users', 'Кассир', index=True, required=True, default=lambda self: self.env.user) + CashierVATIN = fields.Char(string='ИНН кассира', related='CashierName.inn') + Detailed = fields.Boolean(string='Полный отчет итогов по картам', default=True) + UseTerminal = fields.Boolean(string='Использовать терминал', default=True) + timeout_term = fields.Integer(string='Ожидание терминала, мин', default=3) + + def suggest(self): + for s in self: + url = s.UrlServer + headers = { "Authorization": "Basic " + base64.b64encode(s.User + ":" + s.Password) } + data = { + "Command": "List", + "NumDevice": 0, + "InnKkm": "", + "Active": True, + "OnOff": True, + "OFD_Error": False, + } + r = requests.post(url, data=json.dumps(data), headers=headers) + return r.json() diff --git a/kkm_server_plus/security/ir.model.access.csv b/kkm_server_plus/security/ir.model.access.csv new file mode 100644 index 0000000..67f6ff2 --- /dev/null +++ b/kkm_server_plus/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_kkm_server_user,kkm_server.user,base.model_res_users,base.group_user,1,0,0,0 diff --git a/kkm_server_plus/static/description/icon.png b/kkm_server_plus/static/description/icon.png new file mode 100644 index 0000000..49ade68 Binary files /dev/null and b/kkm_server_plus/static/description/icon.png differ diff --git a/kkm_server_plus/static/src/js/All_js.js b/kkm_server_plus/static/src/js/All_js.js new file mode 100644 index 0000000..629c334 --- /dev/null +++ b/kkm_server_plus/static/src/js/All_js.js @@ -0,0 +1,98 @@ +var glSelf; +var CashierName; +var CashierVATIN; +odoo.define('kkm_server.PaymentScreen', function(require) { + "use strict"; + + const { _t } = require('web.core'); + const PaymentScreen = require('point_of_sale.PaymentScreen'); + const Registries = require('point_of_sale.Registries'); + const NumberBuffer = require('point_of_sale.NumberBuffer'); + + const PosKKMPaymentScreen = (PaymentScreen) => class extends PaymentScreen { + setup() { + super.setup(); + glSelf = this; + this.UrlServer = this.env.pos.config.UrlServer; + if (this.UrlServer=="http://localhost:5893"){this.UrlServer = window.location.protocol + "//" + window.location.hostname + ":5893";} + this.User = this.env.pos.config.User; + this.Password = this.env.pos.config.Password; + this.InnKkm_fiscal=""; + if (this.env.pos.config.InnKkm_fiscal!=undefined && this.env.pos.config.InnKkm_fiscal!=false){ + this.InnKkm_fiscal=this.env.pos.config.InnKkm_fiscal; + } + this.InnKkm_term = ""; + if (this.env.pos.config.InnKkm_term!=undefined && this.env.pos.config.InnKkm_term!=false){ + this.InnKkm_term=this.env.pos.config.InnKkm_term; + } + this.NumDevice_fiscal = this.env.pos.config.NumDevice_fiscal; + this.NumDevice_term = this.env.pos.config.NumDevice_term; + this.IsBarCode = this.env.pos.config.IsBarCode; + this.DoubleReceipt = this.env.pos.config.DoubleReceipt; + } + /** + * @override + */ + async validateOrder(isForceValidate) { + console.log('validateOrder'); + LogJS('info','validateOrder') + var enable_kkm_server = this.env.pos.config.enable_kkm_server + var order = this.env.pos.get_order(); + var quantities = _.map(order.get_orderlines(), function (line) {return line.quantity; }); + var plus=0; + var minus = 0; + var type=0; + var NumDevice = 0 + if (this.env.pos.config.NumDevice_fiscal!=undefined && this.env.pos.config.NumDevice_fiscal!=false){ + NumDevice=this.env.pos.config.NumDevice_fiscal + } + var InnKkm = '' + if (this.env.pos.config.InnKkm_fiscal!=undefined && this.env.pos.config.InnKkm_fiscal!=false){ + InnKkm=this.env.pos.config.InnKkm_fiscal + } + var paymentlines = order.get_paymentlines(); + var Amount = 0; + for (var i = 0; i < paymentlines.length; i++) { + if (paymentlines[i].payment_method.type=="bank") {Amount = Amount + Math.abs(paymentlines[i].amount);} + }; + + if (enable_kkm_server){ + for (var i = 0; i < quantities.length; i++) { + if (quantities[i]>0){plus=plus+1;} + if (quantities[i]<0){minus=minus+1;} + if (plus>0 && minus==0){type=0;} + if (plus==0 && minus>0){type=1;} + if (plus>0 && minus>0){type=99;} + } + console.log('type',type); + LogJS('info','type: '+type) + if (type==99){ + alert("Ошибка! Нельзя в одном чеке делать приход и возврат."); + return; + } + else { + var res = this.env.services.rpc({ + route: '/cashier/info', + params: { + cashier_id: this.env.pos.get_cashier_user_id(), + }, + }, { shadow: true }).then((value) => { CashierName = value['name']; + CashierVATIN = value['inn']; + RegisterCheck(NumDevice, type, this.IsBarCode,this.UrlServer,this.User,this.Password,order,CashierName,CashierVATIN,InnKkm,this.DoubleReceipt) + }).catch((err) => { + CashierName = this.env.pos.config.CashierName[1]; + CashierVATIN = this.env.pos.config.CashierVATIN; + RegisterCheck(NumDevice, type, this.IsBarCode,this.UrlServer,this.User,this.Password,order,CashierName,CashierVATIN,InnKkm,this.DoubleReceipt) + }); + //RegisterCheck(NumDevice, type, this.IsBarCode,this.UrlServer,this.User,this.Password,order,this.CashierName,this.CashierVATIN,InnKkm) + } + } + await super.validateOrder(isForceValidate); + } + + }; + + Registries.Component.extend(PaymentScreen, PosKKMPaymentScreen); + + return PaymentScreen; +}); diff --git a/kkm_server_plus/static/src/js/CancelPaymentByPaymentCard.js b/kkm_server_plus/static/src/js/CancelPaymentByPaymentCard.js new file mode 100644 index 0000000..778dcb5 --- /dev/null +++ b/kkm_server_plus/static/src/js/CancelPaymentByPaymentCard.js @@ -0,0 +1,396 @@ +var CounGetRezult; +var IdCommand; +var UrlServer; +var Password; +var User; +var self; +var ord; +var timeout_term; +var glSelf; + +odoo.define('pos_button.cancel_payment_by_payment_card_kkm', function(require) { +'use strict'; + const { Gui } = require('point_of_sale.Gui'); + const PosComponent = require('point_of_sale.PosComponent'); + const { identifyError } = require('point_of_sale.utils'); + const ProductScreen = require('point_of_sale.ProductScreen'); + const { useListener } = require("@web/core/utils/hooks"); + const Registries = require('point_of_sale.Registries'); + const PaymentScreen = require('point_of_sale.PaymentScreen'); + const Chrome = require('point_of_sale.Chrome'); + + const KKM_Cancel_Payment_By_Payment_Card = (PaymentScreen) => + class extends PaymentScreen { + + KKM_Cancel_Payment_By_Payment_Card_click() { + self = this; + glSelf = this; + console.log('KKM_Cancel_Payment_By_Payment_Card_click'); + LogJS('info','KKM_Cancel_Payment_By_Payment_Card_click') + UrlServer = this.env.pos.config.UrlServer; + if (UrlServer=="http://localhost:5893"){UrlServer = window.location.protocol + "//" + window.location.hostname + ":5893";} + User = this.env.pos.config.User; + Password = this.env.pos.config.Password; + var NumDevice = 0; + if (this.env.pos.config.NumDevice_term!=undefined && this.env.pos.config.NumDevice_term!=false){ + NumDevice=this.env.pos.config.NumDevice_term; + } + timeout_term = this.env.pos.config.timeout_term; + if (timeout_term<1){ + timeout_term=1 + } + + var order = this.env.pos.get_order(); + ord = order; + var InnKkm = ""; + if (this.env.pos.config.InnKkm_term!=undefined && this.env.pos.config.InnKkm_term!=false){ + InnKkm=this.env.pos.config.InnKkm_term + } + var paymentlines = order.get_paymentlines(); + var Amount = 0; + for (var i = 0; i < paymentlines.length; i++) { + if (paymentlines[i].payment_method.type=="bank") {Amount = Amount + Math.abs(paymentlines[i].amount);} + }; + if (order.orderlines.length && order.orderlines[0].refunded_orderline_id) { + //alert(this.env.pos.toRefundLines) + var refundDetail = this.env.pos.toRefundLines[order.orderlines[0].refunded_orderline_id]; + var refund_id = refundDetail.orderline.orderBackendId + this.env.services.rpc({ + route: '/order/get_order_info', + params: { + order_id: refund_id, + }, + }, { shadow: true }).then((value) => { + var UniversalID = '' + if (value.UniversalID!=undefined && value.UniversalID!=false){ + UniversalID = value.UniversalID + } + else{ + alert('Не удалось получить идентификатор банковской транзакции из заказа.') + return + } + order.set_kkm_UniversalIDRefund(UniversalID) + CancelPaymentByPaymentCard(UrlServer,User,Password,InnKkm,Amount,NumDevice,UniversalID,order); + }).catch((err) => { + alert('Произошла ошибка при попытке получить информацию о возврате.' + err); + return; + }); + } + else{ + alert('Не выбран заказ для отмены.'); + } + + + } + }; + + Registries.Component.extend(PaymentScreen, KKM_Cancel_Payment_By_Payment_Card); + return KKM_Cancel_Payment_By_Payment_Card; +}); + +function CancelPaymentByPaymentCard(UrlServer,User,Password,InnKkm,Amount,NumDevice,UniversalID,order) { + console.log('CancelPaymentByPaymentCard'); + LogJS('info','CancelPaymentByPaymentCard') + var IdCommandOnly = guid() + console.log('guid отмены платежа: ',IdCommandOnly); + LogJS('warning','guid отмены платежа: '+IdCommandOnly) + order.set_kkm_pay_cancel_kkm_guid(IdCommandOnly) + var Data = { + Command: "CancelPaymentByPaymentCard", + InnKkm: InnKkm, + NumDevice: NumDevice, + Amount: Amount, + UniversalID: UniversalID, + IdCommand: IdCommandOnly + }; + console.log('Data',JSON.stringify(Data)); + LogJS('warning','Data: '+JSON.stringify(Data)) + ExecuteCommandCancel(UrlServer,User,Password,Data,self,ExecuteSuccessPayCancelOnly); + //ExecuteCommand(UrlServer,User,Password,Data); +}; + +function ExecuteSuccessPayCancelOnly(Rezult, textStatus, jqXHR) { + console.log('ExecuteSuccessPayCancelOnly'); + LogJS('info','ExecuteSuccessPayCancelOnly') + console.log('Rezult: ',JSON.stringify(Rezult)); + LogJS('warning','Rezult: '+JSON.stringify(Rezult)) + console.log('Status: ',Rezult.Status); + LogJS('warning','Status: '+Rezult.Status) + var state = Rezult.Status; + if (state == 1 || state == 4) { // значит команда еще выполняется или еще не запустилась + alert('Терминал находится в режиме ожидания. Проверьте статус возврата платежа позже.'); + } else { + if (Rezult.Status==0){ + if (Rezult.Slip!=undefined){ + console.log('Slip: ',Rezult.Slip); + LogJS('warning','Slip: '+Rezult.Slip) + //var test = " ИП Свистунова А.Н. \r\n 353560, Краснодарский край, \r\n Славянский р-н, г \r\n Славянск-на-Кубани, \r\n Красная/Дзержинского, -, - \r\nЧЕК: 0012 ОПЛАТА ПОКУПКИ\r\n15.11.23 16:54:31\r\nТЕРМИНАЛ: 92556988\r\nКАРТА: MIR W\r\n ** 0494\r\nAID: A0000006581010 TSI: 4800\r\nTVR: 8080008000 CDA: ----\r\nСУММА (RUB) 41.00\r\n ОДОБРЕНО \r\nКОД ОТВЕТА: 000\r\nКОД АВТОРИЗАЦИИ: 4952NE\r\nССЫЛКА: 331959317247 KVR: ----\r\n================================\r\n\r" + //alert(test); + var res_short = Rezult.Slip.toLowerCase(); //test.toLowerCase() + console.log('res_short: ',res_short); + LogJS('warning','res_short: '+res_short) + if (res_short.indexOf('код ответа')>-1){ + res_short = res_short.slice(res_short.indexOf('код ответа')) + res_short = res_short.slice(0,res_short.indexOf('\n')) + res_short = res_short.replace('код ответа','').replace(':','').trim() + if (res_short=='000'){ + console.log('validateOrder start'); + LogJS('info','validateOrder start') + //alert('УСПЕХ'); + self.validateOrder(false) + } + else{ + alert('Оплата не завершена. Попробуйте еще раз или измените тип оплаты.') + return; + } + } + else{ + if (res_short.indexOf('одобрено')>-1){ + console.log('validateOrder start'); + LogJS('info','validateOrder start') + //alert('УСПЕХ'); + self.validateOrder(false) + } + else{ + alert('Оплата не завершена. Попробуйте еще раз или измените тип оплаты') + return; + } + } + } + } + else{ + ord.set_kkm_pay_cancel_kkm_guid(null) + alert('Работа терминала завершилась с ошибкой. ' + Rezult.Error); + } + } + }; + +function ExecuteCommandCancel(UrlServer,User,Password, + Data, + self, + FunSuccess, + FunError, + timeout) { + console.log('ExecuteCommandCancel'); + LogJS('info','ExecuteCommandCancel') + if (FunSuccess === undefined) { + FunSuccess = ExecuteSuccess; + } + if (timeout === undefined) { + timeout = timeout_term * 60000; + } + try { + if (KkmServer != undefined) { + if (typeof (Data) == "string") Data = JSON.parse(Data); + KkmServer.Execute(FunSuccess, Data); + return; + }; + } catch { }; + + var JSon = JSON.stringify(Data); + $.support.cors = true; + var jqXHRvar = $.ajax({ + type: 'POST', + async: true, + timeout: timeout, + url: UrlServer + ((UrlServer == "") ? window.location.protocol + "//" + window.location.host + "/" : "/") + 'Execute', + crossDomain: true, + dataType: 'json', + contentType: 'application/json; charset=UTF-8', + processData: false, + data: JSon, + headers: (User != "" || Password != "") ? { "Authorization": "Basic " + btoa(User + ":" + Password) } : "", + success: FunSuccess, + error: FunError + }); + console.log('Ответ ККМ: ',jqXHRvar); + LogJS('warning','Ответ ККМ: '+jqXHRvar) +} + +function ExecuteSuccessPayCancel(Rezult, textStatus, jqXHR) { + console.log('ExecuteSuccessPayCancel'); + LogJS('info','ExecuteSuccessPayCancel') + console.log('Rezult: ',JSON.stringify(Rezult)); + LogJS('warning','Rezult: '+JSON.stringify(Rezult)) + console.log('Status: ',Rezult.Status); + LogJS('warning','Status: '+Rezult.Status) + if (Rezult.Status == 0) { + MessageStatus = "Успешно"; + } else if (Rezult.Status == 1) { + MessageStatus = "Выполняется"; + } else if (Rezult.Status == 2) { + MessageStatus = "Ошибка!"; + } else if (Rezult.Status == 3) { + MessageStatus = "Данные не найдены!"; + }; + MessageError = Rezult.Error; + + var MessageCheckNumber = Rezult.CheckNumber; + var MessageSessionNumber = Rezult.SessionNumber; + var MessageLineLength = Rezult.LineLength; + var MessageAmount = Rezult.Amount; + + var state = Rezult.Status; + if (state == 1 || state == 4) { // значит команда еще выполняется или еще не запустилась + //Вывод данных что результат еще не выполнен + CounGetRezult = CounGetRezult + 1; + $("#MessageStatus").text("Выполняется: Запрос №:" + CounGetRezult); + var Data = { + Command: "GetRezult", + IdCommand: IdCommand, + }; + console.log('Data',JSON.stringify(Data)); + LogJS('warning','Data: '+JSON.stringify(Data)) + setTimeout(function () { ExecuteCommandSetRezultCancel(UrlServer,User,Password,Data,null,IdCommand, ExecuteSuccessSetRezultCancel, null, false) }, 1000); + } else { + + if (Rezult.Status==0){ + if (Rezult.Slip!=undefined){ + console.log('Slip: ',Rezult.Slip); + LogJS('warning','Slip: '+Rezult.Slip) + //var test = " ИП Свистунова А.Н. \r\n 353560, Краснодарский край, \r\n Славянский р-н, г \r\n Славянск-на-Кубани, \r\n Красная/Дзержинского, -, - \r\nЧЕК: 0012 ОПЛАТА ПОКУПКИ\r\n15.11.23 16:54:31\r\nТЕРМИНАЛ: 92556988\r\nКАРТА: MIR W\r\n ** 0494\r\nAID: A0000006581010 TSI: 4800\r\nTVR: 8080008000 CDA: ----\r\nСУММА (RUB) 41.00\r\n ОДОБРЕНО \r\nКОД ОТВЕТА: 000\r\nКОД АВТОРИЗАЦИИ: 4952NE\r\nССЫЛКА: 331959317247 KVR: ----\r\n================================\r\n\r" + //alert(test); + var res_short = Rezult.Slip.toLowerCase(); //test.toLowerCase() + console.log('res_short: ',res_short); + LogJS('warning','res_short: '+res_short) + if (res_short.indexOf('код ответа')>-1){ + res_short = res_short.slice(res_short.indexOf('код ответа')) + res_short = res_short.slice(0,res_short.indexOf('\n')) + res_short = res_short.replace('код ответа','').replace(':','').trim() + if (res_short=='000'){ + self.validateOrder(false) + } + else{ + alert('Оплата не завершена. Попробуйте еще раз или измените тип оплаты.') + return; + } + } + else{ + if (res_short.indexOf('одобрено')>-1){ + self.validateOrder(false) + } + else{ + alert('Оплата не завершена. Попробуйте еще раз или измените тип оплаты') + return; + } + } + } + } + if (MessageError !="" && MessageError!=undefined) { + alert(MessageStatus+"\nСообщение об ошибке:"+MessageError); + return; + }//{mes=mes+"\n Сообщение об ошибке:"+MessageError} + } + }; + +function ExecuteSuccessSetRezultCancel(Rezult, textStatus, jqX) { + console.log('ExecuteSuccessSetRezultCancel'); + LogJS('info','ExecuteSuccessSetRezultCancel') + console.log('Rezult: ',JSON.stringify(Rezult)); + LogJS('warning','Rezult: '+JSON.stringify(Rezult)) + console.log('Status: ',Rezult.Status); + LogJS('warning','Status: '+Rezult.Status) + var state = Rezult.Status; + if (state == 1 || state == 4) { + CounGetRezult = CounGetRezult + 1; + $("#MessageStatus").text("Выполняется: Запрос №:" + CounGetRezult); + //alert("Выполняется: Запрос №:" + CounGetRezult) + var Data = { + Command: "GetRezult", + IdCommand: IdCommand, + }; + console.log('Data',JSON.stringify(Data)); + LogJS('warning','Data: '+JSON.stringify(Data)) + setTimeout(function () { ExecuteCommandSetRezultCancel(UrlServer,User,Password,Data,null,IdCommand, ExecuteSuccessSetRezultCancel, null, false) }, 1000); + } else { // Rezult.Status <> 1 - значит команда уже выполнена + // Вывод результата выполнения команды + //alert('RezultNew:\n' + JSON.stringify(Rezult)) + //alert('status:\n' + Rezult.Status) + if (state==0){ + if (Rezult.Rezult.UniversalID){ + ord.set_kkm_UniversalID(Rezult.Rezult.UniversalID) + } + //alert('Slip:\n' + Rezult.Rezult.Slip) + if (Rezult.Rezult.Slip!=undefined){ + console.log('Slip: ',Rezult.Rezult.Slip); + LogJS('warning','Slip: '+Rezult.Rezult.Slip) + //var test = " ИП Свистунова А.Н. \r\n 353560, Краснодарский край, \r\n Славянский р-н, г \r\n Славянск-на-Кубани, \r\n Красная/Дзержинского, -, - \r\nЧЕК: 0012 ОПЛАТА ПОКУПКИ\r\n15.11.23 16:54:31\r\nТЕРМИНАЛ: 92556988\r\nКАРТА: MIR W\r\n ** 0494\r\nAID: A0000006581010 TSI: 4800\r\nTVR: 8080008000 CDA: ----\r\nСУММА (RUB) 41.00\r\n ОДОБРЕНО \r\nКОД ОТВЕТА: 000\r\nКОД АВТОРИЗАЦИИ: 4952NE\r\nССЫЛКА: 331959317247 KVR: ----\r\n================================\r\n\r" + var res_short = Rezult.Rezult.Slip.toLowerCase(); + console.log('res_short: ',res_short); + LogJS('warning','res_short: '+res_short) + if (res_short.indexOf('код ответа')>-1){ + res_short = res_short.slice(res_short.indexOf('код ответа')) + res_short = res_short.slice(0,res_short.indexOf('\n')) + res_short = res_short.replace('код ответа','').replace(':','').trim() + if (res_short=='000'){ + self.validateOrder(false) + } + else{ + if (res_short.indexOf('одобрено')>-1){ + self.validateOrder(false) + } + else{ + alert('Оплата не завершена. Попробуйте еще раз или измените тип оплаты') + return; + } + } + } + else{ + if (res_short.indexOf('одобрено')>-1){ + self.validateOrder(false) + } + else{ + alert('Оплата не завершена. Попробуйте еще раз или измените тип оплаты') + return; + } + } + } + else{ + alert('Не вернулся слип чек') + } + } + } + } + + +function ExecuteCommandSetRezultCancel(UrlServer,User,Password, + Data, + currentOrder, + IdCommand, + FunSuccess, + FunError, + timeout) { + console.log('ExecuteCommandSetRezultCancel'); + LogJS('info','ExecuteCommandSetRezultCancel') + if (FunSuccess === undefined) { + FunSuccess = ExecuteSuccess; + } + if (timeout === undefined) { + timeout = timeout_term * 60000; + } + try { + if (KkmServer != undefined) { + if (typeof (Data) == "string") Data = JSON.parse(Data); + KkmServer.Execute(FunSuccess, Data); + return; + }; + } catch { }; + var JSon = JSON.stringify(Data); + $.support.cors = true; + var jqXHRvar = $.ajax({ + type: 'POST', + async: true, + timeout: timeout, + url: UrlServer + ((UrlServer == "") ? window.location.protocol + "//" + window.location.host + "/" : "/") + 'Execute', + crossDomain: true, + dataType: 'json', + contentType: 'application/json; charset=UTF-8', + processData: false, + data: JSon, + headers: (User != "" || Password != "") ? { "Authorization": "Basic " + btoa(User + ":" + Password) } : "", + success: FunSuccess, + error: FunError + }); + console.log('Ответ ККМ: ',jqXHRvar); + LogJS('warning','Ответ ККМ:'+jqXHRvar) +} \ No newline at end of file diff --git a/kkm_server_plus/static/src/js/Cash.js b/kkm_server_plus/static/src/js/Cash.js new file mode 100644 index 0000000..e02dd2f --- /dev/null +++ b/kkm_server_plus/static/src/js/Cash.js @@ -0,0 +1,99 @@ +var glSelf; +var CashierName; +var CashierVATIN; +odoo.define('kkm_server.CashButton', function(require) { + "use strict"; + + const { _t } = require('web.core'); + const PaymentScreen = require('point_of_sale.PaymentScreen'); + const Registries = require('point_of_sale.Registries'); + const NumberBuffer = require('point_of_sale.NumberBuffer'); + const CashMovePopup = require('point_of_sale.CashMovePopup'); + const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup'); + const { _lt } = require('@web/core/l10n/translation'); + const { parse } = require('web.field_utils'); + const { useRef, useState } = owl; + + const PosKKMCashButton = (CashMovePopup) => class extends CashMovePopup { + confirm() { + glSelf = this; + console.log('CashButton'); + LogJS('info','CashButton') + var UrlServer = this.env.pos.config.UrlServer; + if (UrlServer=="http://localhost:5893"){UrlServer = window.location.protocol + "//" + window.location.hostname + ":5893";} + var User = this.env.pos.config.User; + var Password = this.env.pos.config.Password; + var res = super.confirm(); + var Amount = this.state.inputAmount; + var NumDevice = 0 + if (this.env.pos.config.NumDevice_fiscal!=undefined && this.env.pos.config.NumDevice_fiscal!=false){ + NumDevice=this.env.pos.config.NumDevice_fiscal + } + var InnKkm = '' + if (this.env.pos.config.InnKkm_fiscal!=undefined && this.env.pos.config.InnKkm_fiscal!=false){ + InnKkm=this.env.pos.config.InnKkm_fiscal + } + var Command = "" + if (res===undefined){ + return res; + } + if (this.state.inputType === 'out') { + Command = "PaymentCash"; + } + if (this.state.inputType === 'in') { + Command = "DepositingCash"; + } + var res = this.env.services.rpc({ + route: '/cashier/info', + params: { + cashier_id: this.env.pos.get_cashier_user_id(), + }, + }, { shadow: true }).then((value) => { CashierName = value['name']; + CashierVATIN = value['inn']; + if (Command!=""){ + var Data = { + Command: Command, + InnKkm: InnKkm, + NumDevice: NumDevice, + CashierName: CashierName, + CashierVATIN: CashierVATIN, + Amount: Amount, + IdCommand: guid() + }; + console.log('Data',JSON.stringify(Data)); + LogJS('warning','Data: '+JSON.stringify(Data)) + ExecuteCommand(UrlServer,User,Password,Data); + } + return res; + }).catch((err) => { + CashierName = this.env.pos.config.CashierName[1]; + CashierVATIN = this.env.pos.config.CashierVATIN; + if (Command!=""){ + var Data = { + Command: Command, + InnKkm: InnKkm, + NumDevice: NumDevice, + CashierName: CashierName, + CashierVATIN: CashierVATIN, + Amount: Amount, + IdCommand: guid() + }; + console.log('Data',JSON.stringify(Data)); + LogJS('warning','Data: '+JSON.stringify(Data)) + ExecuteCommand(UrlServer,User,Password,Data); + } + return res; + }); + + } + }; + + CashMovePopup.template = 'point_of_sale.CashMovePopup'; + CashMovePopup.defaultProps = { + cancelText: _lt('Cancel'), + title: _lt('Cash In/Out'), + }; + Registries.Component.extend(CashMovePopup, PosKKMCashButton); + + return CashMovePopup; +}); \ No newline at end of file diff --git a/kkm_server_plus/static/src/js/CheckKM.js b/kkm_server_plus/static/src/js/CheckKM.js new file mode 100644 index 0000000..9f39cda --- /dev/null +++ b/kkm_server_plus/static/src/js/CheckKM.js @@ -0,0 +1,97 @@ +var NumDevice; +var InnKkm; +var TaxVariant; +var KktNumber; +var IdCommand; +var UrlServer; +var Password; +var User; +var self; +var order; +var glSelf; + +odoo.define('kkm_server.check_km_kkm', function(require) { +'use strict'; + + const PosComponent = require('point_of_sale.PosComponent'); + const { identifyError } = require('point_of_sale.utils'); + const ProductScreen = require('point_of_sale.ProductScreen'); + const { useListener } = require("@web/core/utils/hooks"); + const Registries = require('point_of_sale.Registries'); + const PaymentScreen = require('point_of_sale.PaymentScreen'); + const Chrome = require('point_of_sale.Chrome'); + const BarcodeReader = require('point_of_sale.BarcodeReader'); + const { get_lot_lines } = require('point_of_sale.models'); + const {PosGlobalState} = require("point_of_sale.models"); + var core = require('web.core'); + var utils = require('web.utils'); + const PosLotSaleProductScreen = require('pos_lot_selection.ProductScreen'); + +// Проверка кодов маркировки +function ValidationMarkingCode(NumDevice,UrlServer,User,Password,InnKkm,product) { + var Data = { + Command: "ValidationMarkingCode", + NumDevice: NumDevice, + InnKkm: InnKkm, + TaxVariant: "", + KktNumber: "", + "GoodCodeDatas": [{ + "Name": product.display_name, + "Barcode": window.selectedLot, + Quantity: 1, + MeasureOfQuantity: 0, + PackageQuantity: null, + }, + ], + IdCommand: guid(), + }; + console.log('Data',JSON.stringify(Data)); + LogJS('warning','Data: '+JSON.stringify(Data)) + ExecuteCommand(UrlServer,User,Password,Data); + +} + +// Barcode scaner + BarcodeReader.include({ + scan: async function (code) { + if (!code) return; + self = this; + glSelf = this; + UrlServer = this.env.pos.config.UrlServer; + if (UrlServer=="http://localhost:5893"){UrlServer = window.location.protocol + "//" + window.location.hostname + ":5893";} + User = this.env.pos.config.User; + Password = this.env.pos.config.Password; + var NumDevice = this.env.pos.config.NumDevice_term; + var InnKkm = ""; + if (this.env.pos.config.InnKkm_term!=undefined && this.env.pos.config.InnKkm_term!=false){ + InnKkm=this.env.pos.config.InnKkm_term + } + const callbacks = Object.keys(this.exclusive_callbacks).length + ? this.exclusive_callbacks + : this.action_callbacks; + let parsed_result = this.barcode_parser.parse_barcode(code); + const product = this.env.pos.db.get_product_by_barcode(code); + if (Array.isArray(parsed_result)) { + [...callbacks.gs1].map(cb => cb(parsed_result)); + } else { + if (callbacks[parsed_result.type]) { + for (const cb of callbacks[parsed_result.type]) { + await cb(parsed_result); + if (window.lot_error == true) { + return; + } + ValidationMarkingCode(NumDevice,UrlServer,User,Password,InnKkm,product); + return; + } + } else if (callbacks.error) { + [...callbacks.error].map(cb => cb(parsed_result)); + } else { + console.warn(`Ignored Barcode scan:`, parsed_result); + } + } + }, + }); + + return BarcodeReader; + +}); \ No newline at end of file diff --git a/kkm_server_plus/static/src/js/CleanGUID.js b/kkm_server_plus/static/src/js/CleanGUID.js new file mode 100644 index 0000000..36e35b0 --- /dev/null +++ b/kkm_server_plus/static/src/js/CleanGUID.js @@ -0,0 +1,40 @@ +var CounGetRezult; +var IdCommand; +var UrlServer; +var Password; +var User; +var self; +var ord; +var timeout_term; +var glSelf; + +odoo.define('pos_button.clean_guid_kkm', function(require) { +'use strict'; + const { Gui } = require('point_of_sale.Gui'); + const PosComponent = require('point_of_sale.PosComponent'); + const { identifyError } = require('point_of_sale.utils'); + const ProductScreen = require('point_of_sale.ProductScreen'); + const { useListener } = require("@web/core/utils/hooks"); + const Registries = require('point_of_sale.Registries'); + const PaymentScreen = require('point_of_sale.PaymentScreen'); + const Chrome = require('point_of_sale.Chrome'); + + const KKM_Clean_GUID = (PaymentScreen) => + class extends PaymentScreen { + + KKM_Clean_GUID_click() { + self = this; + glSelf = this; + console.log('KKM_Clean_GUID_click'); + LogJS('info','KKM_Clean_GUID_click') + var order = this.env.pos.get_order(); + order.set_kkm_pay_kkm_guid(null) + order.set_kkm_pay_return_kkm_guid(null) + order.set_kkm_pay_cancel_kkm_guid(null) + alert('GUID-ы KKM очищены.') + } + }; + + Registries.Component.extend(PaymentScreen, KKM_Clean_GUID); + return KKM_Clean_GUID; +}); \ No newline at end of file diff --git a/kkm_server_plus/static/src/js/ClosePop.js b/kkm_server_plus/static/src/js/ClosePop.js new file mode 100644 index 0000000..95894ca --- /dev/null +++ b/kkm_server_plus/static/src/js/ClosePop.js @@ -0,0 +1,54 @@ +var glSelf; +odoo.define('kkm_server.ClosePosPopup', function(require) { + "use strict"; + + const { _t } = require('web.core'); + const ClosePosPopup = require('point_of_sale.ClosePosPopup'); + const Registries = require('point_of_sale.Registries'); + const NumberBuffer = require('point_of_sale.NumberBuffer'); + + const PosKKMClosePosPopup = (ClosePosPopup) => class extends ClosePosPopup { + + /** + * @override + */ + async closeSession() { + glSelf = this; + console.log('ClosePosPopup'); + LogJS('info','ClosePosPopup') + var enable_kkm_server = this.env.pos.config.enable_kkm_server + var UseTerminal = this.env.pos.config.UseTerminal + if (enable_kkm_server && UseTerminal){ + var self = this; + var UrlServer = this.env.pos.config.UrlServer; + if (UrlServer=="http://localhost:5893"){UrlServer = window.location.protocol + "//" + window.location.hostname + ":5893";} + var User = this.env.pos.config.User; + var Password = this.env.pos.config.Password; + var NumDevice = 0; + if (this.env.pos.config.NumDevice_term!=undefined && this.env.pos.config.NumDevice_term!=false){ + NumDevice=this.env.pos.config.NumDevice_term + } + var InnKkm = ""; + if (this.env.pos.config.InnKkm_term!=undefined && this.env.pos.config.InnKkm_term!=false){ + InnKkm=this.env.pos.config.InnKkm_term + } + var Data = { + Command: "Settlement", + InnKkm: InnKkm, + NumDevice: NumDevice, + IdCommand: guid() + }; + console.log('Data',JSON.stringify(Data)); + LogJS('warning','Data: '+JSON.stringify(Data)) + ExecuteCommand(UrlServer,User,Password,Data); + } + await super.closeSession(); + } + + + }; + + Registries.Component.extend(ClosePosPopup, PosKKMClosePosPopup); + + return PosKKMClosePosPopup; +}); diff --git a/kkm_server_plus/static/src/js/Pay.js b/kkm_server_plus/static/src/js/Pay.js new file mode 100644 index 0000000..543e58f --- /dev/null +++ b/kkm_server_plus/static/src/js/Pay.js @@ -0,0 +1,213 @@ +var glSelf; +var self; + +odoo.define('pos_button.pay_all_kkm', function(require) { +'use strict'; + const { Gui } = require('point_of_sale.Gui'); + const PosComponent = require('point_of_sale.PosComponent'); + const { identifyError } = require('point_of_sale.utils'); + const ProductScreen = require('point_of_sale.ProductScreen'); + const { useListener } = require("@web/core/utils/hooks"); + const Registries = require('point_of_sale.Registries'); + const PaymentScreen = require('point_of_sale.PaymentScreen'); + const Chrome = require('point_of_sale.Chrome'); + + const KKM_Pay_All = (PaymentScreen) => + class extends PaymentScreen { + + KKM_pay_all_click() { + self = this; + glSelf = this; + console.log('KKM_pay_all_click'); + LogJS('info','KKM_pay_all_click') + var order = this.env.pos.get_order(); + var enable_kkm_server = this.env.pos.config.enable_kkm_server + var UseTerminal = this.env.pos.config.UseTerminal + var quantities = _.map(order.get_orderlines(), function (line) {return line.quantity; }); + var plus=0; + var minus = 0; + var type=0; + var Amount = 0; + var paymentlines = order.get_paymentlines(); + for (var i = 0; i < paymentlines.length; i++) { + if (paymentlines[i].payment_method.type=="bank") {Amount = Amount + Math.abs(paymentlines[i].amount);} + }; + if (enable_kkm_server && UseTerminal){ + for (var i = 0; i < quantities.length; i++) { + if (quantities[i]>0){plus=plus+1;} + if (quantities[i]<0){minus=minus+1;} + if (plus>0 && minus==0){type=0;} + if (plus==0 && minus>0){type=1;} + if (plus>0 && minus>0){type=99;} + } + if (type==99){ + alert("Ошибка! Нельзя в одном чеке делать приход и возврат."); + return; + } + if (type==1 & Amount>0) { + console.log('Возврат платежа по карте'); + LogJS('info','Возврат платежа по карте') + if (order.pay_return_kkm_guid!=undefined && order.pay_return_kkm_guid!=false){ + console.log('Возврат платежа'); + LogJS('info','Возврат платежа') + var Data = { + Command: "GetRezult", + IdCommand: order.pay_return_kkm_guid, + }; + console.log('Data',JSON.stringify(Data)); + LogJS('warning','Data: '+JSON.stringify(Data)) + ExecuteCommandSetRezult(UrlServer,User,Password,Data,null,order.pay_return_kkm_guid, ExecuteSuccessSetRezultOnly, null, false) + } + else{ + if (order.pay_cancel_kkm_guid!=undefined && order.pay_cancel_kkm_guid!=false){ + console.log('Отмена платежа'); + LogJS('info','Отмена платежа') + var Data = { + Command: "GetRezult", + IdCommand: order.pay_cancel_kkm_guid, + }; + console.log('Data',JSON.stringify(Data)); + LogJS('warning','Data: '+JSON.stringify(Data)) + ExecuteCommandSetRezult(UrlServer,User,Password,Data,null,order.pay_cancel_kkm_guid, ExecuteSuccessSetRezultOnly, null, false) + } + else{ + alert('Отсутствует guid операции возврата платежа на банковскую карту. Выполните возврат на банковскую карту.') + } + } + } + else{ + if (type==0 & Amount>0){ + console.log('Платеж по карте'); + LogJS('info','Платеж по карте') + if (order.pay_kkm_guid!=undefined && order.pay_kkm_guid!=false){ + var Data = { + Command: "GetRezult", + IdCommand: order.pay_kkm_guid, + }; + console.log('Data',JSON.stringify(Data)); + LogJS('warning','Data: '+JSON.stringify(Data)) + ExecuteCommandSetRezult(UrlServer,User,Password,Data,null,order.pay_kkm_guid, ExecuteSuccessSetRezultOnly, null, false) + } + else{ + this.KKM_Pay_By_Payment_Card_click() + //alert('tut') + //alert('Отсутствует guid операции платежа по банковской карте. Выполните платеж банковской картой.') + } + } + else{ + console.log('Платеж/возврат наличными'); + LogJS('info','Платеж/возврат наличными') + this.validateOrder(false) + } + } + } + else{ + console.log('Операция без применения ккм'); + LogJS('info','Операция без применения ккм') + this.validateOrder(false) + } + } + }; + +// KKM_pay_all_click() { +// var self = this; +// var order = this.env.pos.get_order(); +// var enable_kkm_server = this.env.pos.config.enable_kkm_server +// var quantities = _.map(order.get_orderlines(), function (line) {return line.quantity; }); +// var plus=0; +// var minus = 0; +// var type=0; +// var Amount = 0; +// var paymentlines = order.get_paymentlines(); +// for (var i = 0; i < paymentlines.length; i++) { +// if (paymentlines[i].payment_method.type=="bank") {Amount = Amount + Math.abs(paymentlines[i].amount);} +// }; +// if (enable_kkm_server){ +// for (var i = 0; i < quantities.length; i++) { +// if (quantities[i]>0){plus=plus+1;} +// if (quantities[i]<0){minus=minus+1;} +// if (plus>0 && minus==0){type=0;} +// if (plus==0 && minus>0){type=1;} +// if (plus>0 && minus>0){type=99;} +// } +// if (type==99){ +// alert("Ошибка! Нельзя в одном чеке делать приход и возврат."); +// return; +// } +// if (type==1 & Amount>0) { +// this.KKM_Return_Payment_By_Payment_Card_click() +// } +// else{ +// if (type==0 & Amount>0){ +// this.KKM_Pay_By_Payment_Card_click() +// } +// else{ +// this.validateOrder(false) +// } +// } +// } +// else{ +// this.validateOrder(false) +// } +// } +// }; + + Registries.Component.extend(PaymentScreen, KKM_Pay_All); + return KKM_Pay_All; +}); + +function ExecuteSuccessSetRezultOnly(Rezult, textStatus, jqX) { + console.log('ExecuteSuccessSetRezultOnly'); + LogJS('info','ExecuteSuccessSetRezultOnly') + var state = Rezult.Status; + console.log('Rezult: ',JSON.stringify(Rezult)); + LogJS('warning','Rezult: '+JSON.stringify(Rezult)) + console.log('Status: ',Rezult.Status); + LogJS('warning','Status: '+Rezult.Status) + if (state == 1 || state == 4) { + alert('Терминал еще ожидает платеж. Проверьте статус платежа позже'); + } else { + if (state==0){ + if (Rezult.Rezult.UniversalID){ + ord.set_kkm_UniversalID(Rezult.Rezult.UniversalID) + } + if (Rezult.Rezult.Slip!=undefined){ + console.log('Slip: ',Rezult.Rezult.Slip); + LogJS('warning','Slip: '+Rezult.Rezult.Slip) + var res_short = Rezult.Rezult.Slip.toLowerCase(); + if (res_short.indexOf('код ответа')>-1){ + res_short = res_short.slice(res_short.indexOf('код ответа')) + res_short = res_short.slice(0,res_short.indexOf('\n')) + res_short = res_short.replace('код ответа','').replace(':','').trim() + if (res_short=='000' || res_short=='r00'){ + self.validateOrder(false) + } + else{ + if (res_short.indexOf('одобрено')>-1){ + self.validateOrder(false) + } + else{ + alert('Оплата не завершена. Попробуйте еще раз или измените тип оплаты') + return; + } + } + } + else{ + if (res_short.indexOf('одобрено')>-1){ + self.validateOrder(false) + } + else{ + alert('Оплата не завершена. Попробуйте еще раз или измените тип оплаты') + return; + } + } + } + else{ + alert('Не вернулся слип чек') + } + } + else{ + alert('Работа терминала завершилась с ошибкой'); + } + } + } \ No newline at end of file diff --git a/kkm_server_plus/static/src/js/PayByPaymentCard.js b/kkm_server_plus/static/src/js/PayByPaymentCard.js new file mode 100644 index 0000000..e998f7f --- /dev/null +++ b/kkm_server_plus/static/src/js/PayByPaymentCard.js @@ -0,0 +1,482 @@ +var glSelf; +var CounGetRezult; +var IdCommand; +var UrlServer; +var Password; +var User; +var self; +var ord; +var timeout_term; +var CashierName; +var CashierVATIN; + +odoo.define('pos_button.pay_by_payment_card_kkm', function(require) { +'use strict'; + const { Gui } = require('point_of_sale.Gui'); + const PosComponent = require('point_of_sale.PosComponent'); + const { identifyError } = require('point_of_sale.utils'); + const ProductScreen = require('point_of_sale.ProductScreen'); + const { useListener } = require("@web/core/utils/hooks"); + const Registries = require('point_of_sale.Registries'); + const PaymentScreen = require('point_of_sale.PaymentScreen'); + const Chrome = require('point_of_sale.Chrome'); + + const KKM_Pay_By_Payment_Card = (PaymentScreen) => + class extends PaymentScreen { + + KKM_Pay_By_Payment_Card_click() { + self = this; + glSelf = this; + console.log('KKM_Pay_By_Payment_Card_click'); + LogJS('info','KKM_Pay_By_Payment_Card_click') + UrlServer = this.env.pos.config.UrlServer; + if (UrlServer=="http://localhost:5893"){UrlServer = window.location.protocol + "//" + window.location.hostname + ":5893";} + User = this.env.pos.config.User; + Password = this.env.pos.config.Password; + var NumDevice = 0; + if (this.env.pos.config.NumDevice_term!=undefined && this.env.pos.config.NumDevice_term!=false){ + NumDevice=this.env.pos.config.NumDevice_term + } + var InnKkm = ""; + if (this.env.pos.config.InnKkm_term!=undefined && this.env.pos.config.InnKkm_term!=false){ + InnKkm=this.env.pos.config.InnKkm_term + } + var order = this.env.pos.get_order(); + var paymentlines = order.get_paymentlines(); + var IsBarCode = this.env.pos.config.IsBarCode; + timeout_term = this.env.pos.config.timeout_term; + if (timeout_term<1){ + timeout_term=1 + } + var NumDevice_kkt = 0; + if (this.env.pos.config.NumDevice_fiscal!=undefined && this.env.pos.config.NumDevice_fiscal!=false){ + NumDevice_kkt=this.env.pos.config.NumDevice_fiscal + } + var InnKkm_kkt = '' + if (this.env.pos.config.InnKkm_fiscal!=undefined && this.env.pos.config.InnKkm_fiscal!=false){ + InnKkm_kkt=this.env.pos.config.InnKkm_fiscal + } + var Amount = 0; + for (var i = 0; i < paymentlines.length; i++) { + if (paymentlines[i].payment_method.type=="bank") {Amount = Amount + Math.abs(paymentlines[i].amount);} + }; + if (Amount==0){ + alert('Не указана сумма для оплаты по безналичному расчету'); + return; + } + var currentOrder = this.currentOrder + var quantities = _.map(currentOrder.get_orderlines(), function (line) {return line.quantity; }); + var plus=0; + var minus = 0; + var type=0; + for (var i = 0; i < quantities.length; i++) { + if (quantities[i]>0){plus=plus+1;} + if (quantities[i]<0){minus=minus+1;} + if (plus>0 && minus==0){type=0;} + if (plus==0 && minus>0){type=1;} + if (plus>0 && minus>0){type=99;} + } + if (type!=0){ + alert('Сумма платежей должна быть положительной!') + return; + } + ord = this.currentOrder + var res = this.env.services.rpc({ + route: '/cashier/info', + params: { + cashier_id: this.env.pos.get_cashier_user_id(), + }, + }, { shadow: true }).then((value) => { CashierName = value['name']; + CashierVATIN = value['inn']; + PayByPaymentCard(UrlServer,User,Password,InnKkm,Amount,NumDevice,currentOrder,self,IsBarCode,CashierName,CashierVATIN,NumDevice_kkt,InnKkm_kkt); + }).catch((err) => { + CashierName = this.env.pos.config.CashierName[1]; + CashierVATIN = this.env.pos.config.CashierVATIN; + PayByPaymentCard(UrlServer,User,Password,InnKkm,Amount,NumDevice,currentOrder,self,IsBarCode,CashierName,CashierVATIN,NumDevice_kkt,InnKkm_kkt); + }); + + } + }; + + Registries.Component.extend(PaymentScreen, KKM_Pay_By_Payment_Card); + return KKM_Pay_By_Payment_Card; +}); + + +function PayByPaymentCard(UrlServer,User,Password,InnKkm,Amount,NumDevice,currentOrder,self,IsBarCode,CashierName,CashierVATIN,NumDevice_kkt,InnKkm_kkt) { + console.log('PayByPaymentCard'); + LogJS('info','PayByPaymentCard') + var IdCommandOnly = guid() + console.log('guid платежа: ',IdCommandOnly); + LogJS('warning','guid платежа: '+IdCommandOnly) + currentOrder.set_kkm_pay_kkm_guid(IdCommandOnly) + var Data = { + Command: "PayByPaymentCard", + InnKkm: InnKkm, + NumDevice: NumDevice, + Amount: Amount, + IdCommand: IdCommandOnly, + ReceiptNumber: currentOrder.name, + }; + console.log('Data',JSON.stringify(Data)); + LogJS('warning','Data: '+JSON.stringify(Data)) + ExecuteCommandPay(UrlServer,User,Password,Data,currentOrder,self,IsBarCode,CashierName,CashierVATIN,NumDevice_kkt,InnKkm_kkt,IdCommandOnly,ExecuteSuccessPayOnly); +} + +function ExecuteSuccessPayOnly(Rezult, textStatus, jqX) { + console.log('ExecuteSuccessPayOnly'); + LogJS('info','ExecuteSuccessPayOnly') + console.log('Rezult: ',JSON.stringify(Rezult)); + LogJS('warning','Rezult: '+JSON.stringify(Rezult)) + console.log('Status: ',Rezult.Status); + LogJS('warning','Status: '+Rezult.Status) + var state = Rezult.Status; + if (state==0){ + if (Rezult.UniversalID){ + ord.set_kkm_UniversalID(Rezult.UniversalID) + } + if (Rezult.Slip!=undefined){ + console.log('Slip: ',Rezult.Slip); + LogJS('warning','Slip: '+Rezult.Slip) + //var test = " ИП Свистунова А.Н. \r\n 353560, Краснодарский край, \r\n Славянский р-н, г \r\n Славянск-на-Кубани, \r\n Красная/Дзержинского, -, - \r\nЧЕК: 0012 ОПЛАТА ПОКУПКИ\r\n15.11.23 16:54:31\r\nТЕРМИНАЛ: 92556988\r\nКАРТА: MIR W\r\n ** 0494\r\nAID: A0000006581010 TSI: 4800\r\nTVR: 8080008000 CDA: ----\r\nСУММА (RUB) 41.00\r\n ОДОБРЕНО \r\nКОД ОТВЕТА: 000\r\nКОД АВТОРИЗАЦИИ: 4952NE\r\nССЫЛКА: 331959317247 KVR: ----\r\n================================\r\n\r" + //alert(test) + var res_short = Rezult.Slip.toLowerCase(); //test.toLowerCase() // + //alert('res_short:\n' + res_short) + console.log('res_short: ',res_short); + LogJS('warning','res_short: '+res_short) + if (res_short.indexOf('код ответа')>-1){ + res_short = res_short.slice(res_short.indexOf('код ответа')); + res_short = res_short.slice(0,res_short.indexOf('\n')); + res_short = res_short.replace('код ответа','').replace(':','').trim(); + //alert(res_short) + if (res_short=='000'){ + //alert('УСПЕХ') + console.log('validateOrder start'); + LogJS('info','validateOrder start') + self.validateOrder(false); + } + else{ + alert('Оплата не завершена. Попробуйте еще раз или измените тип оплаты') + return; + } + } + else{ + if (res_short.indexOf('одобрено')>-1){ + //alert('УСПЕХ'); + console.log('validateOrder start'); + LogJS('info','validateOrder start') + self.validateOrder(false); + } + else{ + alert('Оплата не завершена. Попробуйте еще раз или измените тип оплаты'); + return; + } + } + } + else{ + alert('Не вернулся слип чек'); + } + } + else{ + if (state == 1 || state == 4){ + if (Rezult.Slip!=undefined){ + console.log('Slip (статус ожидания. Слип от QR): ',Rezult.Slip); + LogJS('warning','Slip (статус ожидания. Слип от QR): '+Rezult.Slip) + var res_short = Rezult.Slip.toLowerCase(); //test.toLowerCase() // + console.log('res_short: ',res_short); + LogJS('warning','res_short: '+res_short) + if (res_short.indexOf('код ответа')>-1){ + res_short = res_short.slice(res_short.indexOf('код ответа')); + res_short = res_short.slice(0,res_short.indexOf('\n')); + res_short = res_short.replace('код ответа','').replace(':','').trim(); + //alert(res_short) + if (res_short=='000' || res_short=='r00'){ + console.log('validateOrder start'); + LogJS('info','validateOrder start') + self.validateOrder(false); + } + else{ + alert('Оплата не завершена. Попробуйте еще раз или измените тип оплаты') + return; + } + } + else{ + if (res_short.indexOf('одобрено')>-1){ + console.log('validateOrder start'); + LogJS('info','validateOrder start') + self.validateOrder(false); + } + else{ + alert('Оплата не завершена. Попробуйте еще раз или измените тип оплаты'); + return; + } + } + } + else{ + alert('Терминал еще ожидает платеж. Проверьте статус платежа позже.'); + } + } + else{ + //ord.set_kkm_pay_kkm_guid(null) + alert('Работа терминала завершилась с ошибкой. ' + Rezult.Error); + } + } +}; + +//---------------------------------------------------------------------------------------- +// Пример асинхронного запроса для интерактивного ввода данных на сервере +// Рекомендуется как основной способ работы с эквайринговыми терминалами (или с другим оборудованием с интерактивным вводом данных) + +// Оплатить платежной картой +function PayByPaymentCardAsync(UrlServer,User,Password,InnKkm,Amount,NumDevice,currentOrder,self,IsBarCode,CashierName,CashierVATIN,NumDevice_kkt,InnKkm_kkt) { + console.log('PayByPaymentCardAsync'); + LogJS('info','PayByPaymentCardAsync') + CounGetRezult = 0; + IdCommand = guid() + var Data = { + Command: "PayByPaymentCard", + InnKkm: InnKkm, + NumDevice: NumDevice, + Amount: Amount, + IdCommand: IdCommand, + CardNumber: "", + ReceiptNumber: currentOrder.name, + Timeout: 1, + }; + console.log('Data',JSON.stringify(Data)); + LogJS('warning','Data: '+JSON.stringify(Data)) + ExecuteCommandPay(UrlServer,User,Password,Data,currentOrder,self,IsBarCode,CashierName,CashierVATIN,NumDevice_kkt,InnKkm_kkt,IdCommand,ExecuteSuccessPay); +} + +function ExecuteCommandPay(UrlServer,User,Password, + Data, + currentOrder, + self, + IsBarCode, + CashierName, + CashierVATIN, + NumDevice_kkt, + InnKkm_kkt, + IdCommand, + FunSuccess, + FunError, + timeout) { + console.log('ExecuteCommandPay'); + LogJS('info','ExecuteCommandPay') + if (FunSuccess === undefined) { + FunSuccess = ExecuteSuccess; + } + if (timeout === undefined) { + timeout = timeout_term * 60000; + } + try { + if (KkmServer != undefined) { + if (typeof (Data) == "string") Data = JSON.parse(Data); + KkmServer.Execute(FunSuccess, Data); + return; + }; + } catch { }; + var JSon = JSON.stringify(Data); + $.support.cors = true; + var jqXHRvar = $.ajax({ + type: 'POST', + async: true, + timeout: timeout, + url: UrlServer + ((UrlServer == "") ? window.location.protocol + "//" + window.location.host + "/" : "/") + 'Execute', + crossDomain: true, + dataType: 'json', + contentType: 'application/json; charset=UTF-8', + processData: false, + data: JSon, + headers: (User != "" || Password != "") ? { "Authorization": "Basic " + btoa(User + ":" + Password) } : "", + success: FunSuccess, + error: FunError + }); + console.log('Ответ ККМ: ',jqXHRvar); + LogJS('warning','Ответ ККМ: '+jqXHRvar) +} + +function ExecuteCommandSetRezult(UrlServer,User,Password, + Data, + currentOrder, + IdCommand, + FunSuccess, + FunError, + timeout) { + console.log('ExecuteCommandSetRezult'); + LogJS('info','ExecuteCommandSetRezult') + if (FunSuccess === undefined) { + FunSuccess = ExecuteSuccess; + } + if (timeout === undefined) { + timeout = timeout_term * 60000; + } + try { + if (KkmServer != undefined) { + if (typeof (Data) == "string") Data = JSON.parse(Data); + KkmServer.Execute(FunSuccess, Data); + return; + }; + } catch { }; + var JSon = JSON.stringify(Data); + $.support.cors = true; + var jqXHRvar = $.ajax({ + type: 'POST', + async: true, + timeout: timeout, + url: UrlServer + ((UrlServer == "") ? window.location.protocol + "//" + window.location.host + "/" : "/") + 'Execute', + crossDomain: true, + dataType: 'json', + contentType: 'application/json; charset=UTF-8', + processData: false, + data: JSon, + headers: (User != "" || Password != "") ? { "Authorization": "Basic " + btoa(User + ":" + Password) } : "", + success: FunSuccess, + error: FunError + }); + console.log('Ответ ККМ: ',jqXHRvar); + LogJS('warning','Ответ ККМ: '+jqXHRvar) +} + +function ExecuteSuccessSetRezult(Rezult, textStatus, jqX) { + console.log('ExecuteSuccessSetRezult'); + LogJS('info','ExecuteSuccessSetRezult') + var state = Rezult.Status; + console.log('Rezult: ',JSON.stringify(Rezult)); + LogJS('warning','Rezult: '+JSON.stringify(Rezult)) + console.log('Status: ',Rezult.Status); + LogJS('warning','Status: '+Rezult.Status) + if (state == 1 || state == 4) { + CounGetRezult = CounGetRezult + 1; + $("#MessageStatus").text("Выполняется: Запрос №:" + CounGetRezult); + console.log('Выполняется запрос №: ',CounGetRezult); + LogJS('warning','Выполняется запрос №:: '+CounGetRezult) + //alert("Выполняется: Запрос №:" + CounGetRezult) + var Data = { + Command: "GetRezult", + IdCommand: IdCommand, + }; + console.log('Data',JSON.stringify(Data)); + LogJS('warning','Data: '+JSON.stringify(Data)) + setTimeout(function () { ExecuteCommandSetRezult(UrlServer,User,Password,Data,null,IdCommand, ExecuteSuccessSetRezult, null, false) }, 1000); + } else { // Rezult.Status <> 1 - значит команда уже выполнена + // Вывод результата выполнения команды + //alert('RezultNew:\n' + JSON.stringify(Rezult)) + //alert('status:\n' + Rezult.Status) + if (state==0){ + if (Rezult.Rezult.UniversalID){ + ord.set_kkm_UniversalID(Rezult.Rezult.UniversalID) + } + //alert('Slip:\n' + Rezult.Rezult.Slip) + + if (Rezult.Rezult.Slip!=undefined){ + console.log('Slip: ',Rezult.Rezult.Slip); + LogJS('warning','Slip: '+Rezult.Rezult.Slip) + //var test = " ИП Свистунова А.Н. \r\n 353560, Краснодарский край, \r\n Славянский р-н, г \r\n Славянск-на-Кубани, \r\n Красная/Дзержинского, -, - \r\nЧЕК: 0012 ОПЛАТА ПОКУПКИ\r\n15.11.23 16:54:31\r\nТЕРМИНАЛ: 92556988\r\nКАРТА: MIR W\r\n ** 0494\r\nAID: A0000006581010 TSI: 4800\r\nTVR: 8080008000 CDA: ----\r\nСУММА (RUB) 41.00\r\n ОДОБРЕНО \r\nКОД ОТВЕТА: 000\r\nКОД АВТОРИЗАЦИИ: 4952NE\r\nССЫЛКА: 331959317247 KVR: ----\r\n================================\r\n\r" + var res_short = Rezult.Rezult.Slip.toLowerCase(); + if (res_short.indexOf('код ответа')>-1){ + res_short = res_short.slice(res_short.indexOf('код ответа')) + res_short = res_short.slice(0,res_short.indexOf('\n')) + res_short = res_short.replace('код ответа','').replace(':','').trim() + if (res_short=='000'){ + self.validateOrder(false) + } + else{ + if (res_short.indexOf('одобрено')>-1){ + self.validateOrder(false) + } + else{ + alert('Оплата не завершена. Попробуйте еще раз или измените тип оплаты') + return; + } + } + } + else{ + if (res_short.indexOf('одобрено')>-1){ + self.validateOrder(false) + } + else{ + alert('Оплата не завершена. Попробуйте еще раз или измените тип оплаты') + return; + } + } + } + else{ + alert('Не вернулся слип чек') + } + } + } + } + +function ExecuteSuccessPay(Rezult, textStatus, jqX) { + console.log('ExecuteSuccessPay'); + LogJS('info','ExecuteSuccessPay') + //alert(Rezult.Status) + console.log('Rezult: ',JSON.stringify(Rezult)); + LogJS('warning','Rezult: '+JSON.stringify(Rezult)) + console.log('Status: ',Rezult.Status); + LogJS('warning','Status: '+Rezult.Status) + var state = Rezult.Status; + if (state == 1 || state == 4) { // значит команда еще выполняется или еще не запустилась + //Вывод данных что результат еще не выполнен + CounGetRezult = CounGetRezult + 1; + $("#MessageStatus").text("Выполняется: Запрос №:" + CounGetRezult); + var Data = { + Command: "GetRezult", + IdCommand: IdCommand, + }; + console.log('Data',JSON.stringify(Data)); + LogJS('warning','Data: '+JSON.stringify(Data)) + setTimeout(function () { ExecuteCommandSetRezult(UrlServer,User,Password,Data,null,IdCommand, ExecuteSuccessSetRezult, null, false) }, 1000); + } else { // Rezult.Status <> 1 - значит команда уже выполнена + // Вывод результата выполнения команды + //alert('RezultNew:\n' + JSON.stringify(Rezult)) + //alert('status:\n' + Rezult.Status) + if (state==0){ + if (Rezult.UniversalID){ + ord.set_kkm_UniversalID(Rezult.UniversalID) + } + if (Rezult.Slip!=undefined){ + console.log('Slip: ',Rezult.Slip); + LogJS('warning','Slip: '+Rezult.Slip) + //var test = " ИП Свистунова А.Н. \r\n 353560, Краснодарский край, \r\n Славянский р-н, г \r\n Славянск-на-Кубани, \r\n Красная/Дзержинского, -, - \r\nЧЕК: 0012 ОПЛАТА ПОКУПКИ\r\n15.11.23 16:54:31\r\nТЕРМИНАЛ: 92556988\r\nКАРТА: MIR W\r\n ** 0494\r\nAID: A0000006581010 TSI: 4800\r\nTVR: 8080008000 CDA: ----\r\nСУММА (RUB) 41.00\r\n ОДОБРЕНО \r\nКОД ОТВЕТА: 000\r\nКОД АВТОРИЗАЦИИ: 4952NE\r\nССЫЛКА: 331959317247 KVR: ----\r\n================================\r\n\r" + //alert(test) + var res_short = Rezult.Slip.toLowerCase(); //test.toLowerCase() // + //alert('res_short:\n' + res_short) + console.log('res_short: ',res_short); + LogJS('warning','res_short: '+res_short) + if (res_short.indexOf('код ответа')>-1){ + res_short = res_short.slice(res_short.indexOf('код ответа')) + res_short = res_short.slice(0,res_short.indexOf('\n')) + res_short = res_short.replace('код ответа','').replace(':','').trim() + //alert(res_short) + if (res_short=='000'){ + self.validateOrder(false) + //RegisterCheck(NumDevice_kkt, 0, IsBarCode,UrlServer,User,Password,currentOrder,CashierName,CashierVATIN,InnKkm_kkt) + } + else{ + if (res_short.indexOf('одобрено')>-1){ + self.validateOrder(false) + } + else{ + alert('Оплата не завершена. Попробуйте еще раз или измените тип оплаты') + return; + } + } + } + else{ + if (res_short.indexOf('одобрено')>-1){ + self.validateOrder(false) + } + else{ + alert('Оплата не завершена. Попробуйте еще раз или измените тип оплаты') + return; + } + } + } + else{ + alert('Не вернулся слип чек') + } + } + } + } diff --git a/kkm_server_plus/static/src/js/PosModel.js b/kkm_server_plus/static/src/js/PosModel.js new file mode 100644 index 0000000..b1b8c87 --- /dev/null +++ b/kkm_server_plus/static/src/js/PosModel.js @@ -0,0 +1,42 @@ + odoo.define('kkm_server_plus.PosModel', function(require) { + 'use strict'; + + const { Orderline } = require('point_of_sale.models'); + const Registries = require("point_of_sale.Registries"); + var utils = require('web.utils'); + + var round_di = utils.round_decimals; + + const CustomOrderline = (OriginalOrderline) => + class extends OriginalOrderline { + can_be_merged_with(orderline){ + var price = parseFloat(round_di(this.price || 0, this.pos.dp['Product Price']).toFixed(this.pos.dp['Product Price'])); + var order_line_price = orderline.get_product().get_price(orderline.order.pricelist, this.get_quantity()); + order_line_price = round_di(orderline.compute_fixed_price(order_line_price), this.pos.currency.decimal_places); + if( this.get_product().id !== orderline.get_product().id){ //only orderline of the same product can be merged + return false; + }else if(!this.get_unit() || !this.get_unit().is_pos_groupable){ + return false; + }else if(this.get_discount() > 0){ // we don't merge discounted orderlines + return false; + }else if(!utils.float_is_zero(price - order_line_price - orderline.get_price_extra(), + this.pos.currency.decimal_places)){ + return false; + // добавил сюда серийники + }else if(this.product.tracking == 'lot' || this.product.tracking == 'serial' && (this.pos.picking_type.use_create_lots || this.pos.picking_type.use_existing_lots)) { + return false; + }else if (this.description !== orderline.description) { + return false; + }else if (orderline.get_customer_note() !== this.get_customer_note()) { + return false; + } else if (this.refunded_orderline_id) { + return false; + }else{ + return true; + } + } + }; + + Registries.Model.extend(Orderline, CustomOrderline); + return Orderline; + }); \ No newline at end of file diff --git a/kkm_server_plus/static/src/js/ReportX.js b/kkm_server_plus/static/src/js/ReportX.js new file mode 100644 index 0000000..c8d273a --- /dev/null +++ b/kkm_server_plus/static/src/js/ReportX.js @@ -0,0 +1,60 @@ +var mes=""; +var glSelf; + +odoo.define('kkm_server.reportx_kkm', function(require) { +'use strict'; + const { Gui } = require('point_of_sale.Gui'); + const PosComponent = require('point_of_sale.PosComponent'); + const { identifyError } = require('point_of_sale.utils'); + const ProductScreen = require('point_of_sale.ProductScreen'); + const { useListener } = require("@web/core/utils/hooks"); + const Registries = require('point_of_sale.Registries'); + const PaymentScreen = require('point_of_sale.PaymentScreen'); + +class KKM_ReportX extends PosComponent { + setup() { + super.setup(); + useListener('click', this.onClick); + } + async onClick() { + var self = this; + glSelf = this; + console.log('reportx_kkm'); + LogJS('info','reportx_kkm') + var UrlServer = this.env.pos.config.UrlServer; + if (UrlServer=="http://localhost:5893"){UrlServer = window.location.protocol + "//" + window.location.hostname + ":5893";} + var User = this.env.pos.config.User; + var Password = this.env.pos.config.Password; + var NumDevice = 0; + if (this.env.pos.config.NumDevice_fiscal!=undefined && this.env.pos.config.NumDevice_fiscal!=false){ + NumDevice=this.env.pos.config.NumDevice_fiscal + } + var InnKkm = '' + if (this.env.pos.config.InnKkm_fiscal!=undefined && this.env.pos.config.InnKkm_fiscal!=false){ + InnKkm=this.env.pos.config.InnKkm_fiscal + } + XReport(NumDevice,UrlServer,User,Password,InnKkm); + } + } + + KKM_ReportX.template = 'KKM_ReportX'; + ProductScreen.addControlButton({ + component: KKM_ReportX, + condition: function () { + var has_enabled = false; + if (this.env.pos.config.enable_kkm_server) { + has_enabled = true; + } + return has_enabled; + }, + position: ['before', 'RefundButton'], + }); + + Registries.Component.add(KKM_ReportX); + + return KKM_ReportX; +}); + + + + diff --git a/kkm_server_plus/static/src/js/ReturnPaymentByPaymentCard.js b/kkm_server_plus/static/src/js/ReturnPaymentByPaymentCard.js new file mode 100644 index 0000000..6ff46f8 --- /dev/null +++ b/kkm_server_plus/static/src/js/ReturnPaymentByPaymentCard.js @@ -0,0 +1,408 @@ +var CounGetRezult; +var IdCommand; +var UrlServer; +var Password; +var User; +var self; +var ord; +var timeout_term; +var glSelf; + +odoo.define('pos_button.return_payment_by_payment_card_kkm', function(require) { +'use strict'; + const { Gui } = require('point_of_sale.Gui'); + const PosComponent = require('point_of_sale.PosComponent'); + const { identifyError } = require('point_of_sale.utils'); + const ProductScreen = require('point_of_sale.ProductScreen'); + const { useListener } = require("@web/core/utils/hooks"); + const Registries = require('point_of_sale.Registries'); + const PaymentScreen = require('point_of_sale.PaymentScreen'); + const Chrome = require('point_of_sale.Chrome'); + + const KKM_Return_Payment_By_Payment_Card = (PaymentScreen) => + class extends PaymentScreen { + + KKM_Return_Payment_By_Payment_Card_click() { + self = this; + glSelf = this; + console.log('KKM_Return_Payment_By_Payment_Card_click'); + LogJS('info','KKM_Return_Payment_By_Payment_Card_click') + UrlServer = this.env.pos.config.UrlServer; + if (UrlServer=="http://localhost:5893"){UrlServer = window.location.protocol + "//" + window.location.hostname + ":5893";} + User = this.env.pos.config.User; + Password = this.env.pos.config.Password; + var NumDevice = this.env.pos.config.NumDevice_term; + var InnKkm = ""; + if (this.env.pos.config.InnKkm_term!=undefined && this.env.pos.config.InnKkm_term!=false){ + InnKkm=this.env.pos.config.InnKkm_term + } + timeout_term = this.env.pos.config.timeout_term; + if (timeout_term<1){ + timeout_term=1 + } + var order = this.env.pos.get_order(); + ord = order; + var paymentlines = order.get_paymentlines(); + var Amount = 0; + for (var i = 0; i < paymentlines.length; i++) { + if (paymentlines[i].payment_method.type=="bank") {Amount = Amount + Math.abs(paymentlines[i].amount);} + }; + if (Amount==0){ + alert('Не указана сумма для возврата по безналичному расчету'); + return; + } + var quantities = _.map(order.get_orderlines(), function (line) {return line.quantity; }); + var plus=0; + var minus = 0; + var type=0; + for (var i = 0; i < quantities.length; i++) { + if (quantities[i]>0){plus=plus+1;} + if (quantities[i]<0){minus=minus+1;} + if (plus>0 && minus==0){type=0;} + if (plus==0 && minus>0){type=1;} + if (plus>0 && minus>0){type=99;} + } + if (type!=1){ + alert('Сумма платежей должна быть отрицательной!') + return; + } + if (order.orderlines.length && order.orderlines[0].refunded_orderline_id) { + //alert(this.env.pos.toRefundLines) + var refundDetail = this.env.pos.toRefundLines[order.orderlines[0].refunded_orderline_id]; + var refund_id = refundDetail.orderline.orderBackendId + this.env.services.rpc({ + route: '/order/get_order_info', + params: { + order_id: refund_id, + }, + }, { shadow: true }).then((value) => { + var UniversalID = '' + if (value.UniversalID!=undefined && value.UniversalID!=false){ + UniversalID = value.UniversalID + } + else{ + alert('Не удалось получить идентификатор банковской транзакции из заказа.') + return + } + order.set_kkm_UniversalIDRefund(UniversalID) + ReturnPaymentByPaymentCard(UrlServer,User,Password,InnKkm,Amount,NumDevice,UniversalID,self); + }).catch((err) => { + alert('Произошла ошибка при попытке получить информацию о возврате.' + err); + return; + }); + } + else{ + alert('Не выбран заказ для возврата.'); + } + } + }; + + Registries.Component.extend(PaymentScreen, KKM_Return_Payment_By_Payment_Card); + return KKM_Return_Payment_By_Payment_Card; +}); + +function ReturnPaymentByPaymentCard(UrlServer,User,Password,InnKkm,Amount,NumDevice,UniversalID,self) { + console.log('ReturnPaymentByPaymentCard'); + LogJS('info','ReturnPaymentByPaymentCard') + var IdCommandOnly = guid() + console.log('guid платежа: ',IdCommandOnly); + LogJS('warning','guid платежа: '+IdCommandOnly) + ord.set_kkm_pay_return_kkm_guid(IdCommandOnly) + var Data = { + Command: "ReturnPaymentByPaymentCard", + InnKkm: InnKkm, + NumDevice: NumDevice, + Amount: Amount, + UniversalID: UniversalID, + IdCommand: IdCommandOnly, + }; + console.log('Data',JSON.stringify(Data)); + LogJS('warning','Data: '+JSON.stringify(Data)) + ExecuteCommandReturn(UrlServer,User,Password,Data,self,ExecuteSuccessPayReturnOnly); +}; + +function ExecuteSuccessPayReturnOnly(Rezult, textStatus, jqXHR) { + console.log('ExecuteSuccessPayReturnOnly'); + LogJS('info','ExecuteSuccessPayReturnOnly') + console.log('Rezult: ',JSON.stringify(Rezult)); + LogJS('warning','Rezult: '+JSON.stringify(Rezult)) + console.log('Status: ',Rezult.Status); + LogJS('warning','Status: '+Rezult.Status) + var state = Rezult.Status; + if (state == 1 || state == 4) { // значит команда еще выполняется или еще не запустилась + alert('Терминал находится в режиме ожидания. Проверьте статус возврата платежа позже.'); + } else { + if (Rezult.Status==0){ + if (Rezult.Slip!=undefined){ + console.log('Slip: ',Rezult.Slip); + LogJS('warning','Slip: '+Rezult.Slip) + //var test = " ИП Свистунова А.Н. \r\n 353560, Краснодарский край, \r\n Славянский р-н, г \r\n Славянск-на-Кубани, \r\n Красная/Дзержинского, -, - \r\nЧЕК: 0012 ОПЛАТА ПОКУПКИ\r\n15.11.23 16:54:31\r\nТЕРМИНАЛ: 92556988\r\nКАРТА: MIR W\r\n ** 0494\r\nAID: A0000006581010 TSI: 4800\r\nTVR: 8080008000 CDA: ----\r\nСУММА (RUB) 41.00\r\n ОДОБРЕНО \r\nКОД ОТВЕТА: 000\r\nКОД АВТОРИЗАЦИИ: 4952NE\r\nССЫЛКА: 331959317247 KVR: ----\r\n================================\r\n\r" + //alert(test); + var res_short = Rezult.Slip.toLowerCase(); //test.toLowerCase() + console.log('res_short: ',res_short); + LogJS('warning','res_short: '+res_short) + if (res_short.indexOf('код ответа')>-1){ + res_short = res_short.slice(res_short.indexOf('код ответа')) + res_short = res_short.slice(0,res_short.indexOf('\n')) + res_short = res_short.replace('код ответа','').replace(':','').trim() + if (res_short=='000'){ + console.log('validateOrder start'); + LogJS('info','validateOrder start') + //alert('УСПЕХ'); + self.validateOrder(false) + } + else{ + alert('Оплата не завершена. Попробуйте еще раз или измените тип оплаты.') + return; + } + } + else{ + if (res_short.indexOf('одобрено')>-1){ + console.log('validateOrder start'); + LogJS('info','validateOrder start') + //alert('УСПЕХ'); + self.validateOrder(false) + } + else{ + alert('Оплата не завершена. Попробуйте еще раз или измените тип оплаты') + return; + } + } + } + } + else{ + ord.set_kkm_pay_return_kkm_guid(null) + alert('Работа терминала завершилась с ошибкой. ' + Rezult.Error); + } + } + }; + +function ExecuteCommandReturn(UrlServer,User,Password, + Data, + self, + FunSuccess, + FunError, + timeout) { + console.log('ExecuteCommandReturn'); + LogJS('info','ExecuteCommandReturn') + if (FunSuccess === undefined) { + FunSuccess = ExecuteSuccess; + } + if (timeout === undefined) { + timeout = timeout_term * 60000; + } + try { + if (KkmServer != undefined) { + if (typeof (Data) == "string") Data = JSON.parse(Data); + KkmServer.Execute(FunSuccess, Data); + return; + }; + } catch { }; + + var JSon = JSON.stringify(Data); + $.support.cors = true; + var jqXHRvar = $.ajax({ + type: 'POST', + async: true, + timeout: timeout, + url: UrlServer + ((UrlServer == "") ? window.location.protocol + "//" + window.location.host + "/" : "/") + 'Execute', + crossDomain: true, + dataType: 'json', + contentType: 'application/json; charset=UTF-8', + processData: false, + data: JSon, + headers: (User != "" || Password != "") ? { "Authorization": "Basic " + btoa(User + ":" + Password) } : "", + success: FunSuccess, + error: FunError + }); + console.log('Ответ ККМ: ',jqXHRvar); + LogJS('warning','Ответ ККМ: '+jqXHRvar) +} + +function ExecuteSuccessPayReturn(Rezult, textStatus, jqXHR) { + console.log('ExecuteSuccessPayReturn'); + LogJS('info','ExecuteSuccessPayReturn') + console.log('Rezult: ',JSON.stringify(Rezult)); + LogJS('warning','Rezult: '+JSON.stringify(Rezult)) + console.log('Status: ',Rezult.Status); + LogJS('warning','Status: '+Rezult.Status) + if (Rezult.Status == 0) { + MessageStatus = "Успешно"; + } else if (Rezult.Status == 1) { + MessageStatus = "Выполняется"; + } else if (Rezult.Status == 2) { + MessageStatus = "Ошибка!"; + } else if (Rezult.Status == 3) { + MessageStatus = "Данные не найдены!"; + }; + MessageError = Rezult.Error; + + var MessageCheckNumber = Rezult.CheckNumber; + var MessageSessionNumber = Rezult.SessionNumber; + var MessageLineLength = Rezult.LineLength; + var MessageAmount = Rezult.Amount; + + var state = Rezult.Status; + if (state == 1 || state == 4) { // значит команда еще выполняется или еще не запустилась + //Вывод данных что результат еще не выполнен + CounGetRezult = CounGetRezult + 1; + $("#MessageStatus").text("Выполняется: Запрос №:" + CounGetRezult); + var Data = { + Command: "GetRezult", + IdCommand: IdCommand, + }; + console.log('Data',JSON.stringify(Data)); + LogJS('warning','Data: '+JSON.stringify(Data)) + setTimeout(function () { ExecuteCommandSetRezultReturn(UrlServer,User,Password,Data,null,IdCommand, ExecuteSuccessSetRezultReturn, null, false) }, 1000); + } else { + + if (Rezult.Status==0){ + if (Rezult.Slip!=undefined){ + console.log('Slip: ',Rezult.Slip); + LogJS('warning','Slip: '+Rezult.Slip) + //var test = " ИП Свистунова А.Н. \r\n 353560, Краснодарский край, \r\n Славянский р-н, г \r\n Славянск-на-Кубани, \r\n Красная/Дзержинского, -, - \r\nЧЕК: 0012 ОПЛАТА ПОКУПКИ\r\n15.11.23 16:54:31\r\nТЕРМИНАЛ: 92556988\r\nКАРТА: MIR W\r\n ** 0494\r\nAID: A0000006581010 TSI: 4800\r\nTVR: 8080008000 CDA: ----\r\nСУММА (RUB) 41.00\r\n ОДОБРЕНО \r\nКОД ОТВЕТА: 000\r\nКОД АВТОРИЗАЦИИ: 4952NE\r\nССЫЛКА: 331959317247 KVR: ----\r\n================================\r\n\r" + //alert(test); + var res_short = Rezult.Slip.toLowerCase(); //test.toLowerCase() + console.log('res_short: ',res_short); + LogJS('warning','res_short: '+res_short) + if (res_short.indexOf('код ответа')>-1){ + res_short = res_short.slice(res_short.indexOf('код ответа')) + res_short = res_short.slice(0,res_short.indexOf('\n')) + res_short = res_short.replace('код ответа','').replace(':','').trim() + if (res_short=='000'){ + self.validateOrder(false) + } + else{ + alert('Оплата не завершена. Попробуйте еще раз или измените тип оплаты.') + return; + } + } + else{ + if (res_short.indexOf('одобрено')>-1){ + self.validateOrder(false) + } + else{ + alert('Оплата не завершена. Попробуйте еще раз или измените тип оплаты') + return; + } + } + } + } + if (MessageError !="" && MessageError!=undefined) { + alert(MessageStatus+"\nСообщение об ошибке:"+MessageError); + return; + }//{mes=mes+"\n Сообщение об ошибке:"+MessageError} + } + }; + +function ExecuteSuccessSetRezultReturn(Rezult, textStatus, jqX) { + console.log('ExecuteSuccessSetRezultReturn'); + LogJS('info','ExecuteSuccessSetRezultReturn') + console.log('Rezult: ',JSON.stringify(Rezult)); + LogJS('warning','Rezult: '+JSON.stringify(Rezult)) + console.log('Status: ',Rezult.Status); + LogJS('warning','Status: '+Rezult.Status) + var state = Rezult.Status; + if (state == 1 || state == 4) { + CounGetRezult = CounGetRezult + 1; + $("#MessageStatus").text("Выполняется: Запрос №:" + CounGetRezult); + //alert("Выполняется: Запрос №:" + CounGetRezult) + var Data = { + Command: "GetRezult", + IdCommand: IdCommand, + }; + console.log('Data',JSON.stringify(Data)); + LogJS('warning','Data: '+JSON.stringify(Data)) + setTimeout(function () { ExecuteCommandSetRezultReturn(UrlServer,User,Password,Data,null,IdCommand, ExecuteSuccessSetRezultReturn, null, false) }, 1000); + } else { // Rezult.Status <> 1 - значит команда уже выполнена + // Вывод результата выполнения команды + //alert('RezultNew:\n' + JSON.stringify(Rezult)) + //alert('status:\n' + Rezult.Status) + if (state==0){ + if (Rezult.Rezult.UniversalID){ + ord.set_kkm_UniversalID(Rezult.Rezult.UniversalID) + } + //alert('Slip:\n' + Rezult.Rezult.Slip) + if (Rezult.Rezult.Slip!=undefined){ + console.log('Slip: ',Rezult.Rezult.Slip); + LogJS('warning','Slip: '+Rezult.Rezult.Slip) + //var test = " ИП Свистунова А.Н. \r\n 353560, Краснодарский край, \r\n Славянский р-н, г \r\n Славянск-на-Кубани, \r\n Красная/Дзержинского, -, - \r\nЧЕК: 0012 ОПЛАТА ПОКУПКИ\r\n15.11.23 16:54:31\r\nТЕРМИНАЛ: 92556988\r\nКАРТА: MIR W\r\n ** 0494\r\nAID: A0000006581010 TSI: 4800\r\nTVR: 8080008000 CDA: ----\r\nСУММА (RUB) 41.00\r\n ОДОБРЕНО \r\nКОД ОТВЕТА: 000\r\nКОД АВТОРИЗАЦИИ: 4952NE\r\nССЫЛКА: 331959317247 KVR: ----\r\n================================\r\n\r" + var res_short = Rezult.Rezult.Slip.toLowerCase(); + console.log('res_short: ',res_short); + LogJS('warning','res_short: '+res_short) + if (res_short.indexOf('код ответа')>-1){ + res_short = res_short.slice(res_short.indexOf('код ответа')) + res_short = res_short.slice(0,res_short.indexOf('\n')) + res_short = res_short.replace('код ответа','').replace(':','').trim() + if (res_short=='000'){ + self.validateOrder(false) + } + else{ + if (res_short.indexOf('одобрено')>-1){ + self.validateOrder(false) + } + else{ + alert('Оплата не завершена. Попробуйте еще раз или измените тип оплаты') + return; + } + } + } + else{ + if (res_short.indexOf('одобрено')>-1){ + self.validateOrder(false) + } + else{ + alert('Оплата не завершена. Попробуйте еще раз или измените тип оплаты') + return; + } + } + } + else{ + alert('Не вернулся слип чек') + } + } + } + } + + +function ExecuteCommandSetRezultReturn(UrlServer,User,Password, + Data, + currentOrder, + IdCommand, + FunSuccess, + FunError, + timeout) { + console.log('ExecuteCommandSetRezultReturn'); + LogJS('info','ExecuteCommandSetRezultReturn') + if (FunSuccess === undefined) { + FunSuccess = ExecuteSuccess; + } + if (timeout === undefined) { + timeout = timeout_term * 60000; + } + try { + if (KkmServer != undefined) { + if (typeof (Data) == "string") Data = JSON.parse(Data); + KkmServer.Execute(FunSuccess, Data); + return; + }; + } catch { }; + var JSon = JSON.stringify(Data); + $.support.cors = true; + var jqXHRvar = $.ajax({ + type: 'POST', + async: true, + timeout: timeout, + url: UrlServer + ((UrlServer == "") ? window.location.protocol + "//" + window.location.host + "/" : "/") + 'Execute', + crossDomain: true, + dataType: 'json', + contentType: 'application/json; charset=UTF-8', + processData: false, + data: JSon, + headers: (User != "" || Password != "") ? { "Authorization": "Basic " + btoa(User + ":" + Password) } : "", + success: FunSuccess, + error: FunError + }); + console.log('Ответ ККМ: ',jqXHRvar); + LogJS('warning','Ответ ККМ: '+jqXHRvar) +} \ No newline at end of file diff --git a/kkm_server_plus/static/src/js/TerminalReport.js b/kkm_server_plus/static/src/js/TerminalReport.js new file mode 100644 index 0000000..6be78c1 --- /dev/null +++ b/kkm_server_plus/static/src/js/TerminalReport.js @@ -0,0 +1,48 @@ +var glSelf; + +odoo.define('kkm_server.ClosePosPopupTerminalReport', function(require) { + "use strict"; + + const { _t } = require('web.core'); + const ClosePosPopup = require('point_of_sale.ClosePosPopup'); + const Registries = require('point_of_sale.Registries'); + const NumberBuffer = require('point_of_sale.NumberBuffer'); + + const PosKKMClosePosPopupTerminalReport = (ClosePosPopup) => class extends ClosePosPopup { + + async TerminalReport_click() { + var self = this; + glSelf = this; + console.log('TerminalReport_click'); + LogJS('info','TerminalReport_click') + var UrlServer = this.env.pos.config.UrlServer; + if (UrlServer=="http://localhost:5893"){UrlServer = window.location.protocol + "//" + window.location.hostname + ":5893";} + var User = this.env.pos.config.User; + var Password = this.env.pos.config.Password; + var NumDevice = 0; + if (this.env.pos.config.NumDevice_term!=undefined && this.env.pos.config.NumDevice_term!=false){ + NumDevice=this.env.pos.config.NumDevice_term + } + var InnKkm = ""; + if (this.env.pos.config.InnKkm_term!=undefined && this.env.pos.config.InnKkm_term!=false){ + InnKkm=this.env.pos.config.InnKkm_term + } + var Detailed = this.env.pos.config.Detailed + var Data = { + Command: "TerminalReport", + InnKkm: InnKkm, + NumDevice: NumDevice, + Detailed: Detailed, + IdCommand: guid() + }; + console.log('Data',JSON.stringify(Data)); + LogJS('warning','Data: '+JSON.stringify(Data)) + ExecuteCommand(UrlServer,User,Password,Data); + } + + }; + + Registries.Component.extend(ClosePosPopup, PosKKMClosePosPopupTerminalReport); + + return PosKKMClosePosPopupTerminalReport; +}); \ No newline at end of file diff --git a/kkm_server_plus/static/src/js/UniversalIDUpdate.js b/kkm_server_plus/static/src/js/UniversalIDUpdate.js new file mode 100644 index 0000000..88c914b --- /dev/null +++ b/kkm_server_plus/static/src/js/UniversalIDUpdate.js @@ -0,0 +1,85 @@ +/** @odoo-module **/ + +import models from 'point_of_sale.models'; +import {Order} from 'point_of_sale.models'; +import Registries from "point_of_sale.Registries"; +const KKMUniversalID = (Order) => class KKMUniversalID extends Order { + constructor() { + super(...arguments); + this.UniversalID = this.UniversalID || null; + this.UniversalIDRefund = this.UniversalIDRefund || null; + this.pay_kkm_guid = this.pay_kkm_guid || null; + this.pay_cancel_kkm_guid = this.pay_cancel_kkm_guid || null; + this.pay_return_kkm_guid = this.pay_return_kkm_guid || null; + } + + set_kkm_UniversalID(UniversalID){ + this.UniversalID = UniversalID + } + + set_kkm_UniversalIDRefund(UniversalIDRefund){ + this.UniversalIDRefund = UniversalIDRefund + } + + set_kkm_pay_kkm_guid(pay_kkm_guid){ + this.pay_kkm_guid = pay_kkm_guid + } + + set_kkm_pay_cancel_kkm_guid(pay_cancel_kkm_guid){ + this.pay_cancel_kkm_guid = pay_cancel_kkm_guid + } + + set_kkm_pay_return_kkm_guid(pay_return_kkm_guid){ + this.pay_return_kkm_guid = pay_return_kkm_guid + } + + export_as_JSON() { + const json = super.export_as_JSON(...arguments) + json.UniversalID = this.UniversalID; + json.UniversalIDRefund = this.UniversalIDRefund; + json.pay_kkm_guid = this.pay_kkm_guid; + json.pay_cancel_kkm_guid = this.pay_cancel_kkm_guid; + json.pay_return_kkm_guid = this.pay_return_kkm_guid; + return json; + } + + init_from_JSON(json) { + super.init_from_JSON(...arguments); + this.UniversalID = json.UniversalID; + this.UniversalIDRefund = json.UniversalIDRefund; + this.pay_kkm_guid = json.pay_kkm_guid; + this.pay_cancel_kkm_guid = json.pay_cancel_kkm_guid; + this.pay_return_kkm_guid = json.pay_return_kkm_guid; + } + + is_electronic_pay_kkm(){ + var paymentlines = this.get_paymentlines(); + var Amount = 0; + for (var i = 0; i < paymentlines.length; i++) { + if (paymentlines[i].payment_method.type=="bank") {Amount = Amount + Math.abs(paymentlines[i].amount);} + }; + if (Amount<0) { + return false + } + else{ + return true + } + } + + is_electronic_pay_kkm_exist(){ + var paymentlines = this.get_paymentlines(); + var Amount = 0; + for (var i = 0; i < paymentlines.length; i++) { + if (paymentlines[i].payment_method.type=="bank") {Amount = Amount + Math.abs(paymentlines[i].amount);} + }; + if (Amount!=0) { + return true + } + else{ + return false + } + } + + }; + + Registries.Model.extend(Order, KKMUniversalID); \ No newline at end of file diff --git a/kkm_server_plus/static/src/js/close.js b/kkm_server_plus/static/src/js/close.js new file mode 100644 index 0000000..d6f4583 --- /dev/null +++ b/kkm_server_plus/static/src/js/close.js @@ -0,0 +1,479 @@ + +var glSelf; +var CashierName; +var CashierVATIN; + +odoo.define('kkm_server.close_kkm', function(require) { +'use strict'; + +const { Gui } = require('point_of_sale.Gui'); +const PosComponent = require('point_of_sale.PosComponent'); +const { identifyError } = require('point_of_sale.utils'); +const ProductScreen = require('point_of_sale.ProductScreen'); +const { useListener } = require("@web/core/utils/hooks"); +const Registries = require('point_of_sale.Registries'); +const PaymentScreen = require('point_of_sale.PaymentScreen'); +const { get_lot_lines } = require('point_of_sale.models'); +const { remove_orderline } = require('point_of_sale.models'); + +// вызывает поп-ап при неверном коде ЧЗ +window.myErrorPopup = function(serverResponse) { + // Use the Gui object here + Gui.showPopup('ErrorPopup', { + 'title': 'Ошибка ЧЗ', + 'body': `${serverResponse} \n Данный товар будет удален из корзины.`, + }); + }; +// удаляет продукт при неверном коде ЧЗ +window.removeCurrentOrderline = function() { + var order = glSelf.env.pos.get_order(); + var orderlines = order.get_orderlines(); + if (orderlines.length) { + var orderline = orderlines[orderlines.length - 1]; + order.remove_orderline(orderline); + } +}; + +class KKM_Close extends PosComponent { + setup() { + super.setup(); + useListener('click', this.onClick); + } + + async onClick() { + var self = this; + glSelf = this; + console.log('close_kkm'); + LogJS('info','close_kkm') + var UrlServer = this.env.pos.config.UrlServer; + if (UrlServer=="http://localhost:5893"){UrlServer = window.location.protocol + "//" + window.location.hostname + ":5893";} + var User = this.env.pos.config.User; + var Password = this.env.pos.config.Password; + var NumDevice = 0; + if (this.env.pos.config.NumDevice_fiscal!=undefined && this.env.pos.config.NumDevice_fiscal!=false){ + NumDevice=this.env.pos.config.NumDevice_fiscal + } + var InnKkm = '' + if (this.env.pos.config.InnKkm_fiscal!=undefined && this.env.pos.config.InnKkm_fiscal!=false){ + InnKkm=this.env.pos.config.InnKkm_fiscal + } + var order = self.env.pos.get_order(); + var NotPrint = this.env.pos.config.NotPrint; + var IsBarCode = this.env.pos.config.IsBarCode; + var XRep = this.env.pos.config.XRep; + var ZRep = this.env.pos.config.ZRep; + var res = this.env.services.rpc({ + route: '/cashier/info', + params: { + cashier_id: this.env.pos.get_cashier_user_id(), + }, + }, { shadow: true }).then((value) => { CashierName = value['name']; + CashierVATIN = value['inn']; + if (XRep == true) XReport(NumDevice,UrlServer,User,Password,InnKkm); + if (ZRep == false) CloseShift(NumDevice,CashierName,CashierVATIN,NotPrint,UrlServer,User,Password,InnKkm); + if (ZRep == true) ZReport(NumDevice,UrlServer,User,Password,InnKkm); + }).catch((err) => { + CashierName = this.env.pos.config.CashierName[1]; + CashierVATIN = this.env.pos.config.CashierVATIN; + if (XRep == true) XReport(NumDevice,UrlServer,User,Password,InnKkm); + if (ZRep == false) CloseShift(NumDevice,CashierName,CashierVATIN,NotPrint,UrlServer,User,Password,InnKkm); + if (ZRep == true) ZReport(NumDevice,UrlServer,User,Password,InnKkm); + }); + } + } + +KKM_Close.template = 'KKM_Close'; + ProductScreen.addControlButton({ + component: KKM_Close, + condition: function () { + var has_enabled = false; + if (this.env.pos.config.enable_kkm_server) { + has_enabled = true; + } + return has_enabled; + }, + position: ['after', 'SetSaleOrderButton'], + }); + + Registries.Component.add(KKM_Close); + + return KKM_Close; + +}); + +function ExecuteCommand(UrlServer,User,Password, + Data, + currentOrder, + FunSuccess, + FunError, + timeout) { + console.log('ExecuteCommand'); + LogJS('info','ExecuteCommand') + if (FunSuccess === undefined) { + FunSuccess = ExecuteSuccess; + } + if (timeout === undefined) { + timeout = 60000; + } + try { + if (KkmServer != undefined) { + if (typeof (Data) == "string") Data = JSON.parse(Data); + KkmServer.Execute(FunSuccess, Data); + return; + }; + } catch { }; + var JSon = JSON.stringify(Data); + $.support.cors = true; + var jqXHRvar = $.ajax({ + type: 'POST', + async: true, + timeout: timeout, + url: UrlServer + ((UrlServer == "") ? window.location.protocol + "//" + window.location.host + "/" : "/") + 'Execute', + crossDomain: true, + dataType: 'json', + contentType: 'application/json; charset=UTF-8', + processData: false, + data: JSon, + headers: (User != "" || Password != "") ? { "Authorization": "Basic " + btoa(User + ":" + Password) } : "", + success: function (Rezult, textStatus, jqXHR) { + if (Rezult.Status == 0) { + MessageStatus = "Успешно"; + } else if (Rezult.Status == 1) { + MessageStatus = "Выполняется"; + } else if (Rezult.Status == 2) { + MessageStatus = "Ошибка"; + } else if (Rezult.Status == 3) { + MessageStatus = "Данные не найдены"; + }; + MessageError = Rezult.Error; + + var MessageCheckNumber = Rezult.CheckNumber; + var MessageSessionNumber = Rezult.SessionNumber; + var MessageLineLength = Rezult.LineLength; + var MessageAmount = Rezult.Amount; + if (currentOrder!=undefined && Rezult.UniversalID!=undefined){ + currentOrder.set_kkm_UniversalID(Rezult.UniversalID); + } + // mes = MessageStatus; + if (MessageError !="" && MessageError!=undefined) { + alert(MessageStatus+"\nСообщение об ошибке:"+MessageError); + return; + }//{mes=mes+"\n Сообщение об ошибке:"+MessageError} + //if (MessageStatus =="Успешно" && MessageError=="") {mes=""} + },//FunSuccess, + error: FunError + }); + console.log('Ответ ККМ:',jqXHRvar); + LogJS('warning','Ответ ККМ:' +jqXHRvar) +} + +//Обработка возвращаемых данных +function ExecuteSuccess(Rezult, textStatus, jqXHR) { + console.log('ExecuteSuccess'); + LogJS('info','ExecuteSuccess') + console.log('Rezult',JSON.stringify(Rezult)); + LogJS('warning','Rezult: '+JSON.stringify(Rezult)) + var ValidationResult; + if (Rezult.Status == 0) { + MessageStatus = "Успешно"; + if (Rezult.MarkingCodeValidation!=undefined){ + var i; + for (i = 0; i < Rezult.MarkingCodeValidation.length; i++) { + if (Rezult.MarkingCodeValidation[i].ValidationPR.ValidationResult||Rezult.MarkingCodeValidation[i].ValidationPR.ValidationDisabled){ + console.log('Код маркировки прошел проверку') + } else { + var serverResponse = Rezult.MarkingCodeValidation[i].ValidationPR.DecryptionResult; + console.log('Код маркировки НЕ прошел проверку'); + // попап с ошибкой + window.myErrorPopup(serverResponse); + // удалить продукт из заказа + window.removeCurrentOrderline(); + } + } + } + } else if (Rezult.Status == 1) { + MessageStatus = "Выполняется"; + } else if (Rezult.Status == 2) { + MessageStatus = "Ошибка"; + } else if (Rezult.Status == 3) { + MessageStatus = "Данные не найдены"; + }; + + MessageError = Rezult.Error; + + var MessageCheckNumber = Rezult.CheckNumber; + var MessageSessionNumber = Rezult.SessionNumber; + var MessageLineLength = Rezult.LineLength; + var MessageAmount = Rezult.Amount; + + if (MessageError !="" && MessageError!=undefined) {alert(MessageStatus+"\nСообщение об ошибке:"+MessageError)}//{mes=mes+"\n Сообщение об ошибке:"+MessageError} + //if (MessageStatus =="Успешно" && MessageError=="") {mes=""} +} + +//Генерация гуида +function guid() { + function S4() { + return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); + } + return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4()); +} + +//Вычисление количества копий чека +function calculateNumberCopies(DoubleReceipt) { + if (DoubleReceipt == true) { + numberCopies = 1; + } else { + numberCopies = 0; + } + return numberCopies; +} + +//Открытие смены +function OpenShift(NumDevice,CashierName,CashierVATIN,NotPrint,UrlServer,User,Password,InnKkm) { + console.log('OpenShift'); + LogJS('info','OpenShift') + var Data = { + Command: "OpenShift", + NumDevice: NumDevice, + InnKkm: InnKkm, + CashierName: CashierName, + CashierVATIN: CashierVATIN, + NotPrint: NotPrint, + IdCommand: guid(), + }; + console.log('Data',JSON.stringify(Data)); + LogJS('warning','Data: '+JSON.stringify(Data)) + ExecuteCommand(UrlServer,User,Password,Data); + //if (mes!="")alert("Открытие смены завершилось с сообщением: " + mes); + //mes=""; +} + +//Закрытие смены +function CloseShift(NumDevice,CashierName,CashierVATIN,NotPrint,UrlServer,User,Password,InnKkm) { + console.log('CloseShift'); + LogJS('info','CloseShift') + var Data = { + Command: "CloseShift", + NumDevice: NumDevice, + InnKkm: InnKkm, + CashierName: CashierName, + CashierVATIN: CashierVATIN, + NotPrint: NotPrint, + IdCommand: guid(), + }; + console.log('Data',JSON.stringify(Data)); + LogJS('warning','Data: '+JSON.stringify(Data)) + ExecuteCommand(UrlServer,User,Password,Data); + //if (mes!="")alert("Закрытие смены завершилось с сообщением: " + mes); + //mes=""; +} + +// Печать X отчета +function XReport(NumDevice,UrlServer,User,Password,InnKkm) { + console.log('XReport'); + LogJS('info','XReport') + var Data = { + Command: "XReport", + NumDevice: NumDevice, + InnKkm: InnKkm, + IdCommand: guid() + }; + console.log('Data',JSON.stringify(Data)); + LogJS('warning','Data: '+JSON.stringify(Data)) + ExecuteCommand(UrlServer,User,Password,Data); + //if (mes!="")alert("Печать Х-Отчета завершилась с сообщением: " + mes); + //mes=""; +} + +// Печать Z отчета +function ZReport(NumDevice,UrlServer,User,Password,InnKkm) { + console.log('ZReport'); + LogJS('info','ZReport') + var Data = { + Command: "ZReport", + NumDevice: NumDevice, + InnKkm: InnKkm, + IdCommand: guid() + }; + console.log('Data',JSON.stringify(Data)); + LogJS('warning','Data: '+JSON.stringify(Data)) + ExecuteCommand(UrlServer,User,Password,Data); + //if (mes!="")alert("Печать Z-Отчета завершилась с сообщением: " + mes); + //mes=""; +} + +// Печать чека +function RegisterCheck(NumDevice,TypeCheck,IsBarCode,UrlServer,User,Password,order,CashierName,CashierVATIN,InnKkm,DoubleReceipt) { + console.log('RegisterCheck'); + LogJS('info','RegisterCheck') + var client = order.get_partner(); + var receipt = order.export_for_printing(); + var orderlines = order.get_orderlines(); + var paymentlines = order.get_paymentlines(); + var Cash = 0; + var ElectronicPayment = 0; + for (var i = 0; i < paymentlines.length; i++) { + if (paymentlines[i].payment_method.type=="cash") {Cash = Cash + Math.abs(paymentlines[i].amount);} + if (paymentlines[i].payment_method.type=="bank") {ElectronicPayment = ElectronicPayment + Math.abs(paymentlines[i].amount);} + }; + var products = _.map(order.get_orderlines(), function (line) {return line.product; }); + var quantities = _.map(order.get_orderlines(), function (line) {return line.quantity; }); + var lot = _.map(order.get_orderlines(), function (line) {return line.get_lot_lines(); }); + var prices = _.map(order.get_orderlines(), function (line) {return line.price; }); + var discounts = _.map(order.get_orderlines(), function (line) {return line.discount; }); + var tax_ids = _.map(order.get_orderlines(), function (line) {return line.get_taxes(); }); + var subtotal = _.map(order.get_orderlines(), function (line) { + return line.price_subtotal_incl; + }); + var print = []; + client_email=""; + client_name=""; + client_barcode=""; + if (client!=undefined && client!=null){ + if (client.email!=undefined && client.email!=null){client_email=client.email;} + if (client.name!=undefined && client.name!=null){client_name=client.name;} + if (client.barcode!=undefined && client.barcode!=null){client_barcode=client.barcode;} + } + for (var i = 0; i < products.length; i++) { + if (lot[i] !== false) { + GoodCodeDataContent = { + BarCode: lot[i][0].lot_name, + ContainsSerialNumber: true, + AcceptOnBad: false, + } + } else { + GoodCodeDataContent = { + BarCode: products.barcode, + ContainsSerialNumber: false, + AcceptOnBad: false, + }} + console.log('GoodCodeDataContent',GoodCodeDataContent); + if (quantities[i]!=0){ + var Amount = (quantities[i]*prices[i]).toFixed(2); //(quantities[i]*prices[i]*(100-discounts[i])/100).toFixed(2); + if (discounts[i]!=undefined && discounts[i]!=null && discounts[i]!=0){ + Amount = (quantities[i]*prices[i]*(100-discounts[i])/100).toFixed(2); + // alert(quantities[i]) + // alert(prices[i]) + // alert(discounts[i]) + // alert(Amount) + } + var Department = 0; + var Tax = -1; + var tax_id = 0 + if (tax_ids.length>0 && tax_ids[i].length>0){tax_id=tax_ids[i][0].amount} + if (tax_id == 10){Tax=10} + if (tax_id == 20){Tax=20} + var SignMethodCalculation = 4; + var SignCalculationObject = 1; + var T = { PrintText: { Text: "<<->>" }, }; + var P = { + Register: { + Name: products[i].display_name, + Quantity: Math.abs(quantities[i]), + Price: Math.abs(prices[i]), + Amount: Math.abs(Amount), + Department: Department, + Tax: Tax, + EAN13: products[i].barcode, + SignMethodCalculation: SignMethodCalculation, + SignCalculationObject: SignCalculationObject, + //MeasurementUnit: "шт",//products[i].display_name , + GoodCodeData: GoodCodeDataContent, + }, + BarCode: { + BarcodeType: "EAN13" , + Barcode: products[i].barcode, + }, + }; + print.push(T); + print.push(P); + } + }; + var RRNCode = "" + var AuthorizationCode = "" + if (order.pay_cancel_kkm_guid!=undefined && order.pay_cancel_kkm_guid!=false && order.pay_cancel_kkm_guid!=null){ + //alert('TUT') + var arrayUniversalIDRefund = order.UniversalIDRefund.split(';') + arrayUniversalIDRefund.forEach(function(item, i, arr) { + var UniversalIDRefundItem = item.split(':') + if (UniversalIDRefundItem[0]==='RRN') RRNCode = UniversalIDRefundItem[1] + if (UniversalIDRefundItem[0]==='AC') AuthorizationCode = UniversalIDRefundItem[1] + //alert(UniversalIDRefundItem[0] + '---' + UniversalIDRefundItem[1]) + //alert( i + ": " + item + " (массив:" + arr + ")" ); + }); + } + //alert('RRNCode: ' + RRNCode) + //alert('AuthorizationCode: ' + AuthorizationCode) + var Data = { + Command: "RegisterCheck", + NumDevice: NumDevice, + InnKkm: InnKkm, + KktNumber: "", + Timeout: 30, + IdCommand: guid(), + IsFiscalCheck: true, + TypeCheck: TypeCheck, + NotPrint: false, + NumberCopies: calculateNumberCopies(DoubleReceipt), // подставляет кол-во копий чека (0 или 1) + CashierName: CashierName, + CashierVATIN: CashierVATIN, + ClientAddress: client_email, + ClientInfo: client_name, + ClientINN: client_barcode, + SenderEmail: "", + PlaceMarket: "", + TaxVariant: "", + PayByProcessing: false, + NumDeviceByProcessing : null, + ReceiptNumber: order.name, + PrintSlipAfterCheck: false, + PrintSlipForCashier: true, + //Если это чек возврата то возможны два поля для отмены транзакции (если не указано то по эквайрингу будет не отмена а возврат оплаты) + RRNCode: RRNCode, // RRNCode из операции эквайринга. Только для отмены оплаты! Для Оплаты или возврата оплаты не заполнять! + AuthorizationCode: AuthorizationCode, // AuthorizationCode из операции эквайринга. Только для отмены оплаты! Для Оплаты или возврата оплаты не заполнять! + CheckStrings: print, + Cash: Cash.toFixed(2), + ElectronicPayment: ElectronicPayment.toFixed(2), + //AdvancePayment: 0, + //Credit: 0, + //CashProvision: 0, + }; + + if (IsBarCode == false) { + for (var i=0; i < Data.CheckStrings.length; i++) { + if (Data.CheckStrings[i] != undefined && Data.CheckStrings[i].BarCode != undefined) { + Data.CheckStrings[i].BarCode = null; + }; + if (Data.CheckStrings[i] != undefined && Data.CheckStrings[i].PrintImage != undefined) { + Data.CheckStrings[i].PrintImage = null; + }; + }; + }; + + + Data.AgentSign = null; + Data.AgentData = null; + Data.PurveyorData = null; + for (var i = 0; i < Data.CheckStrings.length; i++) { + if (Data.CheckStrings[i] != undefined && Data.CheckStrings[i].Register != undefined) { + Data.CheckStrings[i].Register.AgentSign = null; + Data.CheckStrings[i].Register.AgentData = null; + Data.CheckStrings[i].Register.PurveyorData = null; + }; + }; + console.log('Data',JSON.stringify(Data)); + LogJS('warning','Data: '+JSON.stringify(Data)) + ExecuteCommand(UrlServer,User,Password,Data); +} + +function LogJS(Level, Message) { + glSelf.env.services.rpc({ + route: '/loggerjs/message', + params: { + Level: Level, + Message: Message, + }, + }, { shadow: true }).then((value) => { console.log(value); + }).catch((err) => {console.log(err); + }); +} diff --git a/kkm_server_plus/static/src/js/open.js b/kkm_server_plus/static/src/js/open.js new file mode 100644 index 0000000..53aa23f --- /dev/null +++ b/kkm_server_plus/static/src/js/open.js @@ -0,0 +1,95 @@ +var mes=""; +var glSelf; +var CashierName; +var CashierVATIN; + +odoo.define('kkm_server.open_kkm', function(require) { +'use strict'; + const { Gui } = require('point_of_sale.Gui'); + const PosComponent = require('point_of_sale.PosComponent'); + const { identifyError } = require('point_of_sale.utils'); + const ProductScreen = require('point_of_sale.ProductScreen'); + const { useListener } = require("@web/core/utils/hooks"); + const Registries = require('point_of_sale.Registries'); + const PaymentScreen = require('point_of_sale.PaymentScreen'); + +class KKM_Open extends PosComponent { + setup() { + super.setup(); + useListener('click', this.onClick); + } + + async onClick() { + glSelf = this; + var self = this; + console.log('open_kkm'); + LogJSOpen('info','open_kkm'); + var UrlServer = this.env.pos.config.UrlServer; + if (UrlServer=="http://localhost:5893"){UrlServer = window.location.protocol + "//" + window.location.hostname + ":5893";} + var User = this.env.pos.config.User; + var Password = this.env.pos.config.Password; + var NumDevice = 0; + if (this.env.pos.config.NumDevice_fiscal!=undefined && this.env.pos.config.NumDevice_fiscal!=false){ + NumDevice=this.env.pos.config.NumDevice_fiscal + } + var InnKkm = ''; + if (this.env.pos.config.InnKkm_fiscal!=undefined && this.env.pos.config.InnKkm_fiscal!=false){ + InnKkm=this.env.pos.config.InnKkm_fiscal + } + var order = this.env.pos.get_order(); + var NotPrint = this.env.pos.config.NotPrint; + console.log('order',order); + LogJSOpen('info','order: '+order) + var res = this.env.services.rpc({ + route: '/cashier/info', + params: { + cashier_id: this.env.pos.get_cashier_user_id(), + }, + }, { shadow: true }).then((value) => { CashierName = value['name']; + CashierVATIN = value['inn']; + OpenShift(NumDevice,CashierName,CashierVATIN,NotPrint,UrlServer,User,Password,InnKkm); + }).catch((err) => { + CashierName = this.env.pos.config.CashierName[1]; + CashierVATIN = this.env.pos.config.CashierVATIN; + OpenShift(NumDevice,CashierName,CashierVATIN,NotPrint,UrlServer,User,Password,InnKkm); + }); + + + } + } + +KKM_Open.template = 'KKM_Open'; + ProductScreen.addControlButton({ + component: KKM_Open, + condition: function () { + var has_enabled = false; + if (this.env.pos.config.enable_kkm_server) { + has_enabled = true; + } + return has_enabled; + }, + position: ['before', 'OrderlineCustomerNoteButton'], + }); + + Registries.Component.add(KKM_Open); + + return { + KKM_Open, + Gui: Gui, + }; +}); + +function LogJSOpen(Level, Message) { + glSelf.env.services.rpc({ + route: '/loggerjs/message', + params: { + Level: Level, + Message: Message, + }, + }, { shadow: true }).then((value) => { console.log(value); + }).catch((err) => {console.log(err); + }); +} + + + diff --git a/kkm_server_plus/static/src/xml/ClosePop.xml b/kkm_server_plus/static/src/xml/ClosePop.xml new file mode 100644 index 0000000..88a084d --- /dev/null +++ b/kkm_server_plus/static/src/xml/ClosePop.xml @@ -0,0 +1,19 @@ + + + + diff --git a/kkm_server_plus/static/src/xml/PayByPaymentCard.xml b/kkm_server_plus/static/src/xml/PayByPaymentCard.xml new file mode 100644 index 0000000..a41a891 --- /dev/null +++ b/kkm_server_plus/static/src/xml/PayByPaymentCard.xml @@ -0,0 +1,32 @@ + + + + diff --git a/kkm_server_plus/static/src/xml/PosIndex.xml b/kkm_server_plus/static/src/xml/PosIndex.xml new file mode 100644 index 0000000..6afd3bf --- /dev/null +++ b/kkm_server_plus/static/src/xml/PosIndex.xml @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/kkm_server_plus/static/src/xml/UniversalIDUpdate.xml b/kkm_server_plus/static/src/xml/UniversalIDUpdate.xml new file mode 100644 index 0000000..398d880 --- /dev/null +++ b/kkm_server_plus/static/src/xml/UniversalIDUpdate.xml @@ -0,0 +1,10 @@ + + + +
+ + + Click me +
+
+
\ No newline at end of file diff --git a/kkm_server_plus/static/src/xml/service_charge.xml b/kkm_server_plus/static/src/xml/service_charge.xml new file mode 100644 index 0000000..442ce68 --- /dev/null +++ b/kkm_server_plus/static/src/xml/service_charge.xml @@ -0,0 +1,30 @@ + + + + + +
+ Закрыть смену +
+
+ +
+ Открыть смену +
+
+ +
+ X Отчет +
+
+ +
diff --git a/kkm_server_plus/views/pos.xml b/kkm_server_plus/views/pos.xml new file mode 100644 index 0000000..b2528ba --- /dev/null +++ b/kkm_server_plus/views/pos.xml @@ -0,0 +1,124 @@ + + + + pos.order.pos.order.type + pos.order + + + + + + + + + + + + + + + + pos.config.form.service.charge_new + pos.config + + + +
+
+ +
+
+
+
+
+
+
+ + + + res.users.pos.form + res.users + + + + + + + + + +
diff --git a/pos_lot_selection/README.rst b/pos_lot_selection/README.rst new file mode 100755 index 0000000..ac9cccb --- /dev/null +++ b/pos_lot_selection/README.rst @@ -0,0 +1,85 @@ +================= +POS Lot Selection +================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:c4b775ef14871189a94c3978fdbc65f94ca1dafd8389725fdd049cbdf9f85071 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpos-lightgray.png?logo=github + :target: https://github.com/OCA/pos/tree/16.0/pos_lot_selection + :alt: OCA/pos +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/pos-16-0/pos-16-0-pos_lot_selection + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/pos&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to pick between existing lots in POS frontend. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +1. Go to *Inventory > Settings* and set the option *Track lots or serial + numbers* +2. Chose a product that is stockable, go to its *Inventory* + tab, and set *Tracking* to *By Lots*. +3. Go to its *Sales* tab and set it as *Available in the Point of Sale*. +4. Click on *Update Qty On Hand*, chose the same location configured in the + POS you want the lot available in; write a quantity; unfold the *Lot/Serial + Number* field and pick create one if none is available yet. +5. Create a new lot with the serial number of your choice. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Tecnativa +* Camptocamp + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/pos `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/pos_lot_selection/__init__.py b/pos_lot_selection/__init__.py new file mode 100755 index 0000000..0650744 --- /dev/null +++ b/pos_lot_selection/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/pos_lot_selection/__manifest__.py b/pos_lot_selection/__manifest__.py new file mode 100755 index 0000000..446d97f --- /dev/null +++ b/pos_lot_selection/__manifest__.py @@ -0,0 +1,23 @@ +# Copyright 2018 Tecnativa S.L. - David Vidal +# Copyright 2022 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + "name": "POS Lot Selection", + "version": "16.0.1.0.1", + "category": "Point of Sale", + "author": "Tecnativa, Camptocamp, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/pos", + "license": "AGPL-3", + "depends": [ + "point_of_sale", + ], + "assets": { + "point_of_sale.assets": [ + "pos_lot_selection/static/src/js/**/*.js", + "pos_lot_selection/static/src/xml/**/*.xml", + ], + }, + "application": False, + "installable": True, +} diff --git a/pos_lot_selection/i18n/es.po b/pos_lot_selection/i18n/es.po new file mode 100755 index 0000000..e11b30a --- /dev/null +++ b/pos_lot_selection/i18n/es.po @@ -0,0 +1,29 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_lot_selection +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-08-22 18:15+0000\n" +"Last-Translator: Ivorra78 \n" +"Language-Team: none\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: pos_lot_selection +#: model:ir.model,name:pos_lot_selection.model_stock_lot +msgid "Lot/Serial" +msgstr "Lote/Número de serie" + +#. module: pos_lot_selection +#. odoo-javascript +#: code:addons/pos_lot_selection/static/src/js/EditListPopup.js:0 +#, python-format +msgid "Lot/Serial Number(s) Required" +msgstr "Lote/Número(s) de serie Obligatorio" diff --git a/pos_lot_selection/i18n/it.po b/pos_lot_selection/i18n/it.po new file mode 100755 index 0000000..dea2688 --- /dev/null +++ b/pos_lot_selection/i18n/it.po @@ -0,0 +1,29 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_lot_selection +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-01-24 17:34+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: pos_lot_selection +#: model:ir.model,name:pos_lot_selection.model_stock_lot +msgid "Lot/Serial" +msgstr "Lotto/seriale" + +#. module: pos_lot_selection +#. odoo-javascript +#: code:addons/pos_lot_selection/static/src/js/EditListPopup.js:0 +#, python-format +msgid "Lot/Serial Number(s) Required" +msgstr "Richiesto numero(i) di lotto/serie" diff --git a/pos_lot_selection/i18n/pos_lot_selection.pot b/pos_lot_selection/i18n/pos_lot_selection.pot new file mode 100755 index 0000000..c54690d --- /dev/null +++ b/pos_lot_selection/i18n/pos_lot_selection.pot @@ -0,0 +1,26 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_lot_selection +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: pos_lot_selection +#: model:ir.model,name:pos_lot_selection.model_stock_lot +msgid "Lot/Serial" +msgstr "" + +#. module: pos_lot_selection +#. odoo-javascript +#: code:addons/pos_lot_selection/static/src/js/EditListPopup.js:0 +#, python-format +msgid "Lot/Serial Number(s) Required" +msgstr "" diff --git a/pos_lot_selection/models/__init__.py b/pos_lot_selection/models/__init__.py new file mode 100755 index 0000000..ee1c64f --- /dev/null +++ b/pos_lot_selection/models/__init__.py @@ -0,0 +1 @@ +from . import stock_lot diff --git a/pos_lot_selection/models/stock_lot.py b/pos_lot_selection/models/stock_lot.py new file mode 100755 index 0000000..974577f --- /dev/null +++ b/pos_lot_selection/models/stock_lot.py @@ -0,0 +1,30 @@ +# Copyright 2022 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo import api, models +from odoo.tools import float_compare + + +class ProductionLot(models.Model): + _inherit = "stock.lot" + + @api.model + def get_available_lots_for_pos(self, product_id, company_id): + lots = self.sudo().search( + [ + "&", + ["product_id", "=", product_id], + "|", + ["company_id", "=", company_id], + ["company_id", "=", False], + ] + ) + + lots = lots.filtered( + lambda l: float_compare( + l.product_qty, 0, precision_digits=l.product_uom_id.rounding + ) + > 0 + ) + + return lots.mapped("name") diff --git a/pos_lot_selection/readme/DESCRIPTION.rst b/pos_lot_selection/readme/DESCRIPTION.rst new file mode 100755 index 0000000..d0ba275 --- /dev/null +++ b/pos_lot_selection/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module allows to pick between existing lots in POS frontend. diff --git a/pos_lot_selection/readme/USAGE.rst b/pos_lot_selection/readme/USAGE.rst new file mode 100755 index 0000000..cb7c750 --- /dev/null +++ b/pos_lot_selection/readme/USAGE.rst @@ -0,0 +1,9 @@ +1. Go to *Inventory > Settings* and set the option *Track lots or serial + numbers* +2. Chose a product that is stockable, go to its *Inventory* + tab, and set *Tracking* to *By Lots*. +3. Go to its *Sales* tab and set it as *Available in the Point of Sale*. +4. Click on *Update Qty On Hand*, chose the same location configured in the + POS you want the lot available in; write a quantity; unfold the *Lot/Serial + Number* field and pick create one if none is available yet. +5. Create a new lot with the serial number of your choice. diff --git a/pos_lot_selection/static/description/icon.png b/pos_lot_selection/static/description/icon.png new file mode 100755 index 0000000..ca2e860 Binary files /dev/null and b/pos_lot_selection/static/description/icon.png differ diff --git a/pos_lot_selection/static/description/index.html b/pos_lot_selection/static/description/index.html new file mode 100755 index 0000000..98522ef --- /dev/null +++ b/pos_lot_selection/static/description/index.html @@ -0,0 +1,429 @@ + + + + + +POS Lot Selection + + + +
+

POS Lot Selection

+ + +

Beta License: AGPL-3 OCA/pos Translate me on Weblate Try me on Runboat

+

This module allows to pick between existing lots in POS frontend.

+

Table of contents

+ +
+

Usage

+
    +
  1. Go to Inventory > Settings and set the option Track lots or serial +numbers
  2. +
  3. Chose a product that is stockable, go to its Inventory +tab, and set Tracking to By Lots.
  4. +
  5. Go to its Sales tab and set it as Available in the Point of Sale.
  6. +
  7. Click on Update Qty On Hand, chose the same location configured in the +POS you want the lot available in; write a quantity; unfold the Lot/Serial +Number field and pick create one if none is available yet.
  8. +
  9. Create a new lot with the serial number of your choice.
  10. +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Tecnativa
  • +
  • Camptocamp
  • +
+
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/pos project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/pos_lot_selection/static/src/js/EditListPopup.js b/pos_lot_selection/static/src/js/EditListPopup.js new file mode 100755 index 0000000..a2f8e46 --- /dev/null +++ b/pos_lot_selection/static/src/js/EditListPopup.js @@ -0,0 +1,23 @@ +/* + Copyright 2022 Camptocamp SA + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +*/ +odoo.define("pos_lot_selection.EditListPopup", function (require) { + "use strict"; + + const EditListPopup = require("point_of_sale.EditListPopup"); + const Registries = require("point_of_sale.Registries"); + + const LotSelectEditListPopup = (OriginalEditListPopup) => + class extends OriginalEditListPopup { + setup() { + super.setup(); + if (this.props.title === this.env._t("Lot/Serial Number(s) Required")) { + this.props.lots = this.env.session.lots; + } + } + }; + + Registries.Component.extend(EditListPopup, LotSelectEditListPopup); + return EditListPopup; +}); diff --git a/pos_lot_selection/static/src/js/OrderWidget.js b/pos_lot_selection/static/src/js/OrderWidget.js new file mode 100755 index 0000000..5e8aefa --- /dev/null +++ b/pos_lot_selection/static/src/js/OrderWidget.js @@ -0,0 +1,29 @@ +/* + Copyright 2022 Camptocamp SA + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +*/ +odoo.define("pos_lot_selection.CustomOrderWidget", function (require) { + "use strict"; + + const Registries = require("point_of_sale.Registries"); + const OrderWidget = require("point_of_sale.OrderWidget"); + + const CustomOrderWidget = (OriginalOrderWidget) => + class extends OriginalOrderWidget { + /** + * @override + */ + async _editPackLotLines(event) { + const orderline = event.detail.orderline; + this.env.session.lots = await this.env.pos.getProductLots( + orderline.product + ); + const res = await super._editPackLotLines(...arguments); + this.env.session.lots = undefined; + return res; + } + }; + + Registries.Component.extend(OrderWidget, CustomOrderWidget); + return OrderWidget; +}); diff --git a/pos_lot_selection/static/src/js/ProductScreen.js b/pos_lot_selection/static/src/js/ProductScreen.js new file mode 100755 index 0000000..7ac0ca0 --- /dev/null +++ b/pos_lot_selection/static/src/js/ProductScreen.js @@ -0,0 +1,51 @@ +/* + Copyright 2022 Camptocamp SA (https://www.camptocamp.com). + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +*/ +odoo.define("pos_lot_selection.ProductScreen", function (require) { + "use strict"; + + const ProductScreen = require("point_of_sale.ProductScreen"); + const Registries = require("point_of_sale.Registries"); + const { Gui } = require('point_of_sale.Gui'); + + window.lotErrorPopup = function(product) { + // Use the Gui object here + Gui.showPopup('ErrorPopup', { + 'title': 'Ошибка ЧЗ', + 'body': `Введенный код маркировки отсутсвует на складе`, + }); + }; + + const PosLotSaleProductScreen = (OriginalProductScreen) => + class extends OriginalProductScreen { + /** + * @override + */ + async _getAddProductOptions(product) { + if (["serial", "lot"].includes(product.tracking)) { + this.env.session.lots = await this.env.pos.getProductLots(product); + window.availableLots = this.env.session.lots; + console.log('this.env.session.lots',this.env.session.lots); + if (this.env.session.lots.length > 0) { + const res = await super._getAddProductOptions(...arguments); + window.selectedLot = res.draftPackLotLines.newPackLotLines[0].lot_name; + if (window.availableLots.includes(window.selectedLot)) { + console.log('window.selectedLot',window.selectedLot); + return res; + } + } + window.lotErrorPopup(product); + window.lot_error = true; + return; + } + const res = await super._getAddProductOptions(...arguments); + this.env.session.lots = undefined; + + return res; + } + }; + + Registries.Component.extend(ProductScreen, PosLotSaleProductScreen); + return ProductScreen; +}); diff --git a/pos_lot_selection/static/src/js/models.js b/pos_lot_selection/static/src/js/models.js new file mode 100755 index 0000000..b128082 --- /dev/null +++ b/pos_lot_selection/static/src/js/models.js @@ -0,0 +1,35 @@ +/* + Copyright 2022 Camptocamp SA + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +*/ +odoo.define("pos_lot_selection.models", function (require) { + "use strict"; + + const {PosGlobalState} = require("point_of_sale.models"); + const Registries = require("point_of_sale.Registries"); + + const LotSelectPosGlobalState = (OriginalPosGlobalState) => + class extends OriginalPosGlobalState { + async getProductLots(product) { + try { + return await this.env.services.rpc( + { + model: "stock.lot", + method: "get_available_lots_for_pos", + kwargs: { + product_id: product.id, + company_id: this.env.session.company_id, + }, + }, + {shadow: true} + ); + } catch (error) { + console.error(error); + return []; + } + } + }; + + Registries.Model.extend(PosGlobalState, LotSelectPosGlobalState); + return PosGlobalState; +}); \ No newline at end of file diff --git a/pos_lot_selection/static/src/xml/LotSelectorPopup.xml b/pos_lot_selection/static/src/xml/LotSelectorPopup.xml new file mode 100755 index 0000000..4636bb5 --- /dev/null +++ b/pos_lot_selection/static/src/xml/LotSelectorPopup.xml @@ -0,0 +1,32 @@ + + + + + + + props.lots + + + + + + prepared_lots + + + + + + + + + + +