diff --git a/pos_esign_request/README.rst b/pos_esign_request/README.rst index d5837bb..8ac1e10 100644 --- a/pos_esign_request/README.rst +++ b/pos_esign_request/README.rst @@ -53,7 +53,8 @@ You need two devices: one for POS, another one for taking signs - Open E-Sign Kiosk on the second device - Open menu ``[[ Point of Sale ]] >> Dashboard`` - - Click ``E-Sign`` on the same POS as opened on the first device + - Click three dots on the same POS as opened on the first device + - Click ``E-Sign`` - On the first device click ``Customer`` button @@ -70,6 +71,13 @@ You need two devices: one for POS, another one for taking signs - On the first device for the customer in the **E-Sign** column ✔ sign appears +Known issues / Roadmap +====================== + +Current implementation technically depends on ``pos_self_order`` module. +Meanwhile no functional feature are used, only technical feature of self +order screen. + Bug Tracker =========== diff --git a/pos_esign_request/__manifest__.py b/pos_esign_request/__manifest__.py index adcf4de..da756f1 100644 --- a/pos_esign_request/__manifest__.py +++ b/pos_esign_request/__manifest__.py @@ -10,16 +10,24 @@ "website": "https://github.com/it-projects-llc/pos-addons", "license": "LGPL-3", "depends": [ - "pos_longpolling", + "pos_self_order", ], + "assets": { + "pos_self_order.assets": [ + "pos_esign_request/static/src/app/**/*", + "web/static/lib/jquery/jquery.js", + "web/static/src/core/signature/name_and_signature.scss", + "web/static/src/core/signature/name_and_signature.xml", + "web/static/src/core/signature/name_and_signature.js", + ], + "point_of_sale._assets_pos": [ + "pos_esign_request/static/src/overrides/**/*", + ], + }, "data": [ - "views/assets.xml", - "views/pos_config_view.xml", "views/partner_views.xml", + "views/pos_config_views.xml", + "views/res_config_settings_views.xml", ], "demo": [], - "qweb": [ - "static/src/xml/pos_esign.xml", - "static/src/xml/est_templates.xml", - ], } diff --git a/pos_esign_request/controllers/main.py b/pos_esign_request/controllers/main.py index 4cb9051..13eecb2 100644 --- a/pos_esign_request/controllers/main.py +++ b/pos_esign_request/controllers/main.py @@ -1,32 +1,29 @@ -import json +from werkzeug.exceptions import Unauthorized from odoo import http -from odoo.http import request +from odoo.http import request, route class PosESignExtension(http.Controller): - @http.route("/pos_longpolling/sign_request", type="json", auth="user") - def sign_request(self, vals): - channel_name = "pos.sign_request.to_est" - config_id = request.env["pos.config"].browse(vals.get("config_id", False)) - if ( - request.env["ir.config_parameter"] + def _verify_pos_config(self, access_token): + pos_config_sudo = ( + request.env["pos.config"] .sudo() - .get_param("pos_longpolling.allow_public") - ): - config_id = config_id.sudo() - - config_id.send_to_esign_tab(channel_name, config_id.id, json.dumps(vals)) + .search([("access_token", "=", access_token)], limit=1) + ) + if not pos_config_sudo or not pos_config_sudo.has_active_session: + raise Unauthorized("Invalid access token") + return pos_config_sudo - @http.route("/pos_longpolling/submit_sign", type="json", auth="user") - def submit_kiosk_sign(self, vals): - config_id = request.env["pos.config"].browse(vals.get("config_id", 0)) + @route("/pos_esign_request/sign_response", type="json", auth="public") + def submit_kiosk_sign(self, access_token, vals): + config = self._verify_pos_config(access_token) res = self.update_partner_sign(vals) - if res and config_id: - channel_name = "pos.sign_request" - config_id._send_to_channel_by_id( - config_id._cr.dbname, config_id.id, channel_name, json.dumps(res) + if res and config.current_session_id: + session = config.current_session_id + request.env["bus.bus"]._sendone( + session._get_bus_channel_name(), "ESIGN_RESPONSE", res ) return True @@ -37,9 +34,11 @@ def update_partner_sign(self, vals): if not (partner_id and sign): return False - partner_id = request.env["res.partner"].browse(int(partner_id)) - ir_attachment = request.env["ir.attachment"] - attachment = ir_attachment.create( + Partners = request.env["res.partner"].sudo() + partner_id = Partners.browse(int(partner_id)) + + Attachments = request.env["ir.attachment"].sudo() + attachment = Attachments.create( { "type": "binary", "name": partner_id.name + "E-Sign", diff --git a/pos_esign_request/models/__init__.py b/pos_esign_request/models/__init__.py index 0650744..6f23194 100644 --- a/pos_esign_request/models/__init__.py +++ b/pos_esign_request/models/__init__.py @@ -1 +1,4 @@ -from . import models +from . import pos_config +from . import pos_session +from . import res_config_settings +from . import res_partner diff --git a/pos_esign_request/models/models.py b/pos_esign_request/models/models.py deleted file mode 100644 index 2793910..0000000 --- a/pos_esign_request/models/models.py +++ /dev/null @@ -1,61 +0,0 @@ -import logging - -from odoo import _, api, fields, models -from odoo.exceptions import UserError - -_logger = logging.getLogger(__name__) - - -class PosConfig(models.Model): - _inherit = "pos.config" - - ask_for_sign = fields.Boolean(string="Ask To Sign", default=False) - mandatory_ask_for_sign = fields.Boolean( - string="Mandatory Ask To Sign", default=False - ) - terms_to_sign = fields.Char(string="Terms & Conditions") - - @api.depends("ask_for_sign") - @api.onchange("ask_for_sign") - def _onchange_ask_for_sign(self): - if not self.ask_for_sign: - self.mandatory_ask_for_sign = False - - @api.model - def send_to_esign_tab(self, channel_name, sub_channel, data): - notifications = self.send_data_by_poll(channel_name, sub_channel, data) - _logger.debug("EST notifications for %s: %s", self.ids, notifications) - return - - @api.model - def send_data_by_poll(self, channel_name, sub_channel, data): - channel = '["%s","%s","%s"]' % (self._cr.dbname, channel_name, sub_channel) - notifications = [[channel, data]] - self.env["bus.bus"].sendmany(notifications) - return notifications - - def open_esign_kiosk(self): - if self.company_id not in self.env.companies: - raise UserError( - _("Current user is not activated on company %s") % self.company_id.name - ) - return { - "name": "E-Sign Kiosk", - "type": "ir.actions.client", - "tag": "est_kiosk_mode", - "target": "fullscreen", - "context": { - "config_id": self.id, - "terms_to_sign": self.terms_to_sign, - "pos_name": self.name, - "company_name": self.company_id.name, - }, - } - - -class ResPartner(models.Model): - """Partners""" - - _inherit = "res.partner" - - sign_attachment_id = fields.Many2one("ir.attachment", "E-Sign") diff --git a/pos_esign_request/models/pos_config.py b/pos_esign_request/models/pos_config.py new file mode 100644 index 0000000..21402a5 --- /dev/null +++ b/pos_esign_request/models/pos_config.py @@ -0,0 +1,45 @@ +from odoo import api, fields, models + + +class PosConfig(models.Model): + _inherit = "pos.config" + + ask_for_sign = fields.Boolean(string="Ask To Sign", default=False) + mandatory_ask_for_sign = fields.Boolean( + string="Mandatory Ask To Sign", default=False + ) + terms_to_sign = fields.Char(string="Terms & Conditions (ESign)") + + def _get_esign_route(self): + self.ensure_one() + base_route = f"/pos-self/{self.id}/esign_kiosk" + return f"{base_route}?access_token={self.access_token}" + + def preview_esign_app(self): + self.ensure_one() + return { + "type": "ir.actions.act_url", + "url": self._get_esign_route(), + "target": "new", + } + + @api.depends("ask_for_sign") + @api.onchange("ask_for_sign") + def _onchange_ask_for_sign(self): + if not self.ask_for_sign: + self.mandatory_ask_for_sign = False + + def sign_request(self, **kw): + for config in self: + if config.current_session_id and config.access_token: + self.env["bus.bus"]._sendone( + f"pos_config-{config.access_token}", "ESIGN_REQUEST", kw + ) + + def _get_self_ordering_data(self): + res = super()._get_self_ordering_data() + res["config"].update( + name=self.name, + terms_to_sign=self.terms_to_sign, + ) + return res diff --git a/pos_esign_request/models/pos_session.py b/pos_esign_request/models/pos_session.py new file mode 100644 index 0000000..5698338 --- /dev/null +++ b/pos_esign_request/models/pos_session.py @@ -0,0 +1,10 @@ +from odoo import models + + +class PosSession(models.Model): + _inherit = "pos.session" + + def _loader_params_res_partner(self): + res = super()._loader_params_res_partner() + res["search_params"]["fields"].append("sign_attachment_id") + return res diff --git a/pos_esign_request/models/res_config_settings.py b/pos_esign_request/models/res_config_settings.py new file mode 100644 index 0000000..2e23c95 --- /dev/null +++ b/pos_esign_request/models/res_config_settings.py @@ -0,0 +1,22 @@ +from odoo import api, fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + pos_ask_for_sign = fields.Boolean( + related="pos_config_id.ask_for_sign", readonly=False + ) + pos_mandatory_ask_for_sign = fields.Boolean( + related="pos_config_id.mandatory_ask_for_sign", readonly=False + ) + pos_terms_to_sign = fields.Char( + related="pos_config_id.terms_to_sign", readonly=False + ) + + @api.onchange("pos_ask_for_sign", "pos_self_ordering_mode") + def _onchange_ask_for_sign(self): + if self.pos_ask_for_sign: + self.pos_self_ordering_mode = "mobile" + else: + self.pos_mandatory_ask_for_sign = False diff --git a/pos_esign_request/models/res_partner.py b/pos_esign_request/models/res_partner.py new file mode 100644 index 0000000..41f1aad --- /dev/null +++ b/pos_esign_request/models/res_partner.py @@ -0,0 +1,7 @@ +from odoo import fields, models + + +class Partner(models.Model): + _inherit = "res.partner" + + sign_attachment_id = fields.Many2one("ir.attachment", "E-Sign") diff --git a/pos_esign_request/readme/ROADMAP.md b/pos_esign_request/readme/ROADMAP.md new file mode 100644 index 0000000..77d1ffc --- /dev/null +++ b/pos_esign_request/readme/ROADMAP.md @@ -0,0 +1,2 @@ +Current implementation technically depends on `pos_self_order` module. +Meanwhile no functional feature are used, only technical feature of self order screen. diff --git a/pos_esign_request/readme/USAGE.md b/pos_esign_request/readme/USAGE.md index e36eb52..6a0410a 100644 --- a/pos_esign_request/readme/USAGE.md +++ b/pos_esign_request/readme/USAGE.md @@ -4,7 +4,8 @@ You need two devices: one for POS, another one for taking signs * Open E-Sign Kiosk on the second device * Open menu `[[ Point of Sale ]] >> Dashboard` - * Click `E-Sign` on the same POS as opened on the first device + * Click three dots on the same POS as opened on the first device + * Click `E-Sign` * On the first device click `Customer` button * Select a Customer diff --git a/pos_esign_request/static/description/index.html b/pos_esign_request/static/description/index.html index 1227909..42b5c79 100644 --- a/pos_esign_request/static/description/index.html +++ b/pos_esign_request/static/description/index.html @@ -376,11 +376,12 @@

POS E-Sign Request

+
+

Known issues / Roadmap

+

Current implementation technically depends on pos_self_order module. +Meanwhile no functional feature are used, only technical feature of self +order screen.

+
-

Bug Tracker

+

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 @@ -428,15 +436,15 @@

Bug Tracker

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

-

Credits

+

Credits

-

Authors

+

Authors

-

Contributors

+

Contributors

-

Maintainers

+

Maintainers

This module is part of the it-projects-llc/pos-addons project on GitHub.

You are welcome to contribute.

diff --git a/pos_esign_request/static/src/app/pages/esign_kiosk_page/esign_kiosk_page.js b/pos_esign_request/static/src/app/pages/esign_kiosk_page/esign_kiosk_page.js new file mode 100644 index 0000000..6df7c0d --- /dev/null +++ b/pos_esign_request/static/src/app/pages/esign_kiosk_page/esign_kiosk_page.js @@ -0,0 +1,61 @@ +/** @odoo-module */ + +import {Component, markup, useState} from "@odoo/owl"; +import {NameAndSignature} from "@web/core/signature/name_and_signature"; +import {useSelfOrder} from "@pos_self_order/app/self_order_service"; +import {useService} from "@web/core/utils/hooks"; + +export class ESignKioskPage extends Component { + static template = "pos_esign_request.ESignKioskPage"; + static components = {NameAndSignature}; + + setup() { + this.selfOrder = useSelfOrder(); + this.router = useService("router"); + this.rpc = useService("rpc"); + this.selfOrder.esignRequest = { + showingTerms: false, + isSubmitting: false, + signature: {}, + }; + this.state = useState(this.selfOrder.esignRequest); + } + + showTerms() { + this.state.showingTerms = true; + } + + hideTerms() { + this.state.showingTerms = false; + } + + get terms() { + return markup(this.selfOrder.config.terms_to_sign); + } + + async onClickSubmit() { + this.state.isSubmitting = true; + + try { + const vals = this.prepareSignValues(); + await this.rpc("/pos_esign_request/sign_response", { + access_token: this.selfOrder.access_token, + vals: vals, + }); + this.state.partner_id = null; + } finally { + this.state.isSubmitting = false; + } + } + + onClickReject() { + this.state.partner_id = null; + } + + prepareSignValues() { + return { + partner_id: this.state.partner_id, + sign: this.state.signature.getSignatureImage()[1], + }; + } +} diff --git a/pos_esign_request/static/src/app/pages/esign_kiosk_page/esign_kiosk_page.scss b/pos_esign_request/static/src/app/pages/esign_kiosk_page/esign_kiosk_page.scss new file mode 100644 index 0000000..f1a1dcb --- /dev/null +++ b/pos_esign_request/static/src/app/pages/esign_kiosk_page/esign_kiosk_page.scss @@ -0,0 +1,41 @@ +.est_kiosk_mode { + width: 100%; + text-align: center; + position: relative; + background-color: #fff; + padding: 2em; + + h1 { + margin: 0 0 2rem 0; + } + + .message_demo_barcodes { + font-size: 0.9em; + margin: 0; + } + + p { + margin: 1rem 0; + } + + img { + overflow: hidden; // prevent margins colapsing with h1 + width: 200px; + } + + > button { + font-size: 1.2em; + margin-bottom: 2rem; + width: 100%; + } + + > button:last-child { + margin-bottom: 0; + } + + .o_web_sign_draw_button, + .o_web_sign_load_button, + .o_web_sign_auto_button { + display: none; + } +} diff --git a/pos_esign_request/static/src/app/pages/esign_kiosk_page/esign_kiosk_page.xml b/pos_esign_request/static/src/app/pages/esign_kiosk_page/esign_kiosk_page.xml new file mode 100644 index 0000000..0473458 --- /dev/null +++ b/pos_esign_request/static/src/app/pages/esign_kiosk_page/esign_kiosk_page.xml @@ -0,0 +1,65 @@ + + + +
+
+

+
+

Terms and Conditions

+

+ +

+
+ Company Logo +

E-Sign Kiosk

+

Related to

+ +

+ Welcome + ! +

+ Draw your signature + +
+ +

Waiting for a sign request

+
+
+
+
+ + + + +
+
+
diff --git a/pos_esign_request/static/src/app/self_order_bus_service.js b/pos_esign_request/static/src/app/self_order_bus_service.js new file mode 100644 index 0000000..7805d70 --- /dev/null +++ b/pos_esign_request/static/src/app/self_order_bus_service.js @@ -0,0 +1,18 @@ +/** @odoo-module */ + +import {SelfOrderBus} from "@pos_self_order/app/self_order_bus_service"; +import {patch} from "@web/core/utils/patch"; + +patch(SelfOrderBus.prototype, { + dispatch(message) { + super.dispatch(...arguments); + + if (message.type === "ESIGN_REQUEST") { + this.ws_esignRequest(message.payload); + } + }, + ws_esignRequest(payload) { + this.selfOrder.esignRequest.signature.name = payload.partner_name; + this.selfOrder.esignRequest.partner_id = payload.partner_id; + }, +}); diff --git a/pos_esign_request/static/src/app/self_order_index.js b/pos_esign_request/static/src/app/self_order_index.js new file mode 100644 index 0000000..395528f --- /dev/null +++ b/pos_esign_request/static/src/app/self_order_index.js @@ -0,0 +1,12 @@ +/** @odoo-module */ + +import selfOrder from "@pos_self_order/app/self_order_index"; +import {ESignKioskPage} from "@pos_esign_request/app/pages/esign_kiosk_page/esign_kiosk_page"; +import {patch} from "@web/core/utils/patch"; + +// I don't know why import { selfOrderIndex} } does not work +const selfOrderIndex = selfOrder.selfOrderIndex; + +patch(selfOrderIndex, { + components: {...selfOrderIndex.components, ESignKioskPage}, +}); diff --git a/pos_esign_request/static/src/app/self_order_index.xml b/pos_esign_request/static/src/app/self_order_index.xml new file mode 100644 index 0000000..c8afa4d --- /dev/null +++ b/pos_esign_request/static/src/app/self_order_index.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/pos_esign_request/static/src/css/esign.css b/pos_esign_request/static/src/css/esign.css deleted file mode 100644 index b60065d..0000000 --- a/pos_esign_request/static/src/css/esign.css +++ /dev/null @@ -1,48 +0,0 @@ -.est_kiosk_mode_container { - background: url("../../../../web_enterprise/static/src/img/application-switcher-bg.jpg") - no-repeat center center fixed; - background-size: cover; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; -} - -.est_kiosk_mode { - width: 100%; - text-align: center; - position: relative; - background-color: #fff; - padding: 2em; - - h1 { - margin: 0 0 2rem 0; - } - - .message_demo_barcodes { - font-size: 0.9em; - margin: 0; - } - - p { - text-align: left; - margin: 3rem 0; - } - - > button { - font-size: 1.2em; - margin-bottom: 2rem; - width: 100%; - } - - > button:last-child { - margin-bottom: 0; - } -} - -.est_kiosk_mode img { - overflow: hidden; // prevent margins colapsing with h1 - margin-top: 3rem; - width: 200px; -} diff --git a/pos_esign_request/static/src/css/pos_esign.css b/pos_esign_request/static/src/css/pos_esign.css deleted file mode 100644 index 7ad21c5..0000000 --- a/pos_esign_request/static/src/css/pos_esign.css +++ /dev/null @@ -1,52 +0,0 @@ -.est_kiosk_mode_container { - background: url("../../../../web_enterprise/static/src/img/application-switcher-bg.jpg") - no-repeat center center fixed; - background-size: cover; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; -} - -.est_kiosk_mode { - width: 100%; - text-align: center; - position: relative; - background-color: #fff; - padding: 2em; - - h1 { - margin: 0 0 2rem 0; - } - - .message_demo_barcodes { - font-size: 0.9em; - margin: 0; - } - - p { - text-align: left; - margin: 3rem 0; - } - - > button { - font-size: 1.2em; - margin-bottom: 2rem; - width: 100%; - } - - > button:last-child { - margin-bottom: 0; - } -} - -.est_kiosk_mode img { - overflow: hidden; // prevent margins colapsing with h1 - margin-top: 3rem; - width: 200px; -} - -.button.esign { - left: 120px; -} diff --git a/pos_esign_request/static/src/js/ClientListScreen.js b/pos_esign_request/static/src/js/ClientListScreen.js deleted file mode 100644 index 8973235..0000000 --- a/pos_esign_request/static/src/js/ClientListScreen.js +++ /dev/null @@ -1,23 +0,0 @@ -odoo.define("pos_esign_request.ClientListScreen", function (require) { - "use strict"; - - var ClientListScreen = require("point_of_sale.ClientListScreen"); - const Registries = require("point_of_sale.Registries"); - - const POSESignRequestClientListScreen = (x) => - class extends x { - mounted() { - super.mounted.apply(this, arguments); - this.env.pos.on("changed:partner_esign", this.render, this); - } - - willUnmount() { - super.willUnmount.apply(this, arguments); - this.env.pos.off("changed:partner_esign", null, this); - } - }; - - Registries.Component.extend(ClientListScreen, POSESignRequestClientListScreen); - - return ClientListScreen; -}); diff --git a/pos_esign_request/static/src/js/PaymentScreen.js b/pos_esign_request/static/src/js/PaymentScreen.js deleted file mode 100644 index ab44a4f..0000000 --- a/pos_esign_request/static/src/js/PaymentScreen.js +++ /dev/null @@ -1,41 +0,0 @@ -odoo.define("pos_esign_request.PaymentScreen", function (require) { - "use strict"; - - const PaymentScreen = require("point_of_sale.PaymentScreen"); - const Registries = require("point_of_sale.Registries"); - - const POSESignRequestPaymentScreen = (x) => - class extends x { - get canCustomerValidatePayment() { - const client = this.currentOrder.get_client(); - // Customer is not set - if (!client) return true; - - // Does not need to ask for sign - if (!this.env.pos.config.ask_for_sign) return true; - - // Customer did sign - if (client.sign_attachment_id) return true; - - // Sign is not mandatory - if (!this.env.pos.config.mandatory_ask_for_sign) return true; - - // Customer did not sign and it is mandatory - return false; - } - - mounted() { - super.mounted.apply(this, arguments); - this.env.pos.on("changed:partner_esign", this.render, this); - } - - willUnmount() { - super.willUnmount.apply(this, arguments); - this.env.pos.off("changed:partner_esign", null, this); - } - }; - - Registries.Component.extend(PaymentScreen, POSESignRequestPaymentScreen); - - return POSESignRequestPaymentScreen; -}); diff --git a/pos_esign_request/static/src/js/esign_mode.js b/pos_esign_request/static/src/js/esign_mode.js deleted file mode 100644 index 858109b..0000000 --- a/pos_esign_request/static/src/js/esign_mode.js +++ /dev/null @@ -1,278 +0,0 @@ -odoo.define("pos_esign_request.esign_mode", function (require) { - "use strict"; - - var core = require("web.core"); - var rpc = require("web.rpc"); - var Widget = require("web.Widget"); - var Session = require("web.session"); - var local_storage = require("web.local_storage"); - var AbstractAction = require("web.AbstractAction"); - - var QWeb = core.qweb; - var _t = core._t; - - var AcceptModalKiosk = Widget.extend({ - events: { - "click #sign_clean": "clearSignature", - }, - initSignature: function (ev) { - var signature = this.kiosk.$el.find("#signature"); - signature.empty().jSignature({ - "decor-color": "#D1D0CE", - color: "#000", - "background-color": "#fff", - }); - this.empty_sign = signature.jSignature("getData", "image"); - }, - clearSignature: function (ev) { - $("#signature").jSignature("reset"); - }, - submitForm: function (ev) { - var self = this; - var $confirm_btn = $("button#submit_sign"); - ev.preventDefault(); - var values = this.compose_vals(); - var is_empty = values.signature - ? this.empty_sign[1] === values.signature[1] - : false; - $("#drawsign") - .toggleClass("panel-danger", is_empty) - .toggleClass("panel-default", !is_empty); - if (is_empty) { - setTimeout(function () { - $confirm_btn.removeAttr("data-loading-text").button("reset"); - }); - return false; - } - - $confirm_btn.prepend(' '); - $confirm_btn.attr("disabled", true); - Session.rpc("/pos_longpolling/submit_sign", { - vals: values, - }).then(function (result) { - self.kiosk.close_sign_form(); - }); - return false; - }, - - compose_vals: function () { - var $drawsign = $("#drawsign"); - var signature = $drawsign.find("#signature").jSignature("getData", "image"); - return { - partner_id: this.kiosk.partner.partner_id, - sign: signature ? JSON.stringify(signature[1]) : false, - config_id: this.kiosk.action.context.config_id, - }; - }, - }); - - var KioskMode = AbstractAction.extend({ - init: function (parent, action) { - this._super(parent, action); - this.parent = parent; - this.action = action; - this.session = Session; - var context = this.action.context; - - if (context.config_id) { - this.save_locally("config_id", context.config_id); - this.save_locally("terms_to_sign", context.terms_to_sign); - this.save_locally("pos_name", context.pos_name); - } else { - context.config_id = this.get_from_storage("config_id"); - context.pos_name = this.get_from_storage("pos_name"); - context.terms_to_sign = this.get_from_storage("terms_to_sign"); - } - }, - - save_locally: function (key, value) { - local_storage.setItem("est." + key, JSON.stringify(value)); - }, - - get_from_storage: function (key) { - return JSON.parse(local_storage.getItem("est." + key)); - }, - - update_bus: function () { - var self = this; - this.bus = this.searchModelConfig.env.services.bus_service; - this.bus.stopPolling(); - var channel_name = "pos.sign_request.to_est"; - this.esign_channel_name = this.get_full_channel_name( - channel_name, - String(this.action.context.config_id) + "" - ); - this.bus.addChannel(this.esign_channel_name); - this.force_start_polling(); - this.bus.onNotification(this.bus, function (data) { - var check = false; - try { - check = - data && - data.length && - JSON.parse(data[0][0])[1] === channel_name; - } catch (error) { - check = false; - } - if (check) { - self.on_est_sign_updates(data); - } - }); - }, - - force_start_polling: function () { - this.bus.startPolling(); - if (!this.bus._isActive) { - this.bus._poll(); - this.bus.stop = false; - } - }, - - get_full_channel_name: function (channel_name, sub_channel) { - return JSON.stringify([Session.db, channel_name, sub_channel]); - }, - - on_est_sign_updates: function (message) { - var self = this; - var options = JSON.parse(message[0][1]); - if (!options.partner_id) { - return; - } - this.set_partner(options); - this.render_client_data(); - }, - - set_partner: function (options) { - this.partner = options; - }, - - render_client_data: function () { - var self = this; - var sign_panel = $("#drawsign"); - - this.$el - .find(".greeting_message") - .text("Welcome " + this.partner.partner_name + "!"); - sign_panel.show(); - this.sign_widget.initSignature(); - }, - - start: function () { - var self = this; - this.company_name = this.action.company_name; - this.company_image_url = this.session.url("/web/image", { - model: "res.company", - id: this.session.company_id, - field: "logo", - }); - - this.$el.html(QWeb.render("ESTKioskMode", {widget: self})); - this.toggle_full_screen(); - this.start_sign_widget(); - // TODO: remove it - $(".o_hr_attendance_button_partners").on("click", function (e) { - this.sign_widget.initSignature(e); - }); - var terms_container = this.$el.find(".terms_container"); - terms_container.find(".terms_text").hide(); - terms_container - .find(".fold_terms") - .hide() - .on("click", function (e) { - terms_container.find(".fold_terms").hide(); - terms_container.find(".terms_text").hide(); - terms_container.find(".unfold_terms").show(); - }); - - terms_container.find(".unfold_terms").on("click", function (e) { - terms_container.find(".fold_terms").show(); - terms_container.find(".terms_text").show(); - terms_container.find(".unfold_terms").hide(); - }); - this.update_bus(); - return this._super.apply(this, arguments); - }, - - toggle_full_screen: function () { - if (!document.webkitIsFullScreen) { - var el = document.documentElement; - var requestMethod = - el.requestFullScreen || - el.webkitRequestFullScreen || - el.mozRequestFullScreen || - el.msRequestFullScreen; - if (requestMethod) { - // Native full screen. - requestMethod.call(el); - } else if (typeof window.ActiveXObject !== "undefined") { - // Older IE. - var wscript = new ActiveXObject("WScript.Shell"); - if (wscript !== null) { - wscript.SendKeys("{F11}"); - } - } - - // Event_toggleFullScreen.toggleFullScreen(document.documentElement); - // anyway hide navbar from others - $("nav").hide(); - } - }, - - start_sign_widget: function () { - var self = this; - - this.sign_widget = new AcceptModalKiosk(); - this.sign_widget.setElement($("#modalaccept")); - this.sign_widget.start(); - this.sign_widget.kiosk = this; - - this.$el.find("#sign_clean").on("click", function (e) { - self.sign_widget.clearSignature(e); - }); - - this.$el.find("#submit_sign").on("click", function (e) { - self.sign_widget.submitForm(e); - }); - - this.$el.find("#reject_sign").on("click", function (e) { - self.close_sign_form(); - }); - }, - - close_sign_form: function () { - var $confirm_btn = this.$el.find("button#submit_sign"); - var $drawsign = $("#drawsign"); - - this.$el.find(".greeting_message").text("Waiting for a sign request"); - $drawsign.hide(); - $("#signature").empty(); - $confirm_btn.find("i.fa.fa-spinner.fa-spin").remove(); - $confirm_btn.attr("disabled", false); - this.partner = false; - }, - - on_barcode_scanned: function (barcode) { - var self = this; - var hr_employee = new Model("res.partner"); - hr_employee.call("attendance_scan", [barcode]).then(function (result) { - if (result.action) { - self.do_action(result.action); - } else if (result.warning) { - self.do_warn(result.warning); - } - }); - }, - - destroy: function () { - clearInterval(this.clock_start); - this._super.apply(this, arguments); - }, - }); - - core.action_registry.add("est_kiosk_mode", KioskMode); - - return { - KioskMode: KioskMode, - AcceptModalKiosk: AcceptModalKiosk, - }; -}); diff --git a/pos_esign_request/static/src/js/pos_esign.js b/pos_esign_request/static/src/js/pos_esign.js deleted file mode 100644 index a6b137e..0000000 --- a/pos_esign_request/static/src/js/pos_esign.js +++ /dev/null @@ -1,73 +0,0 @@ -odoo.define("pos_esign_request.esign_request", function (require) { - "use strict"; - - var Session = require("web.session"); - var models = require("point_of_sale.models"); - - const PosComponent = require("point_of_sale.PosComponent"); - const Registries = require("point_of_sale.Registries"); - const {posbus} = require("point_of_sale.utils"); - - models.load_fields("res.partner", ["sign_attachment_id"]); - - var PosModelSuper = models.PosModel; - models.PosModel = models.PosModel.extend({ - initialize: function () { - var self = this; - PosModelSuper.prototype.initialize.apply(this, arguments); - - this.ready.then(function () { - if (!self.config.ask_for_sign) { - return; - } - var channel_name = "pos.sign_request"; - var callback = self.updates_from_sign_kiosk; - var bus = self.get_bus(); - bus.add_channel_callback(channel_name, self.esign_callback, self); - bus.start(); - }); - }, - - esign_callback: function (res) { - if (!res) { - return; - } - res = JSON.parse(res); - - var partner = this.db.get_partner_by_id(res.partner_id); - if (partner) { - partner.sign_attachment_id = res.attachment_id; - this.trigger("changed:partner_esign", res); - } - }, - - esign_request: function (vals) { - Session.rpc("/pos_longpolling/sign_request", { - vals: vals, - }); - }, - }); - - class ButtonEsign extends PosComponent { - async onClickAttButton() { - var partner = this.props.partner; - if (!partner) { - return; - } - this.env.pos.waiting_for_esign_partner = partner; - this.env.pos.trigger("changed:partner_esign"); - Session.rpc("/pos_longpolling/sign_request", { - vals: { - partner_id: partner.id, - partner_name: partner.name, - config_id: this.env.pos.config.id, - }, - }); - } - } - ButtonEsign.template = "ESignButton"; - - Registries.Component.add(ButtonEsign); - - return ButtonEsign; -}); diff --git a/pos_esign_request/static/src/overrides/models/pos_bus.js b/pos_esign_request/static/src/overrides/models/pos_bus.js new file mode 100644 index 0000000..ee72129 --- /dev/null +++ b/pos_esign_request/static/src/overrides/models/pos_bus.js @@ -0,0 +1,29 @@ +/** @odoo-module */ + +import {PosBus} from "@point_of_sale/app/bus/pos_bus_service"; +import {patch} from "@web/core/utils/patch"; + +patch(PosBus.prototype, { + // Override + dispatch(message) { + super.dispatch(...arguments); + + const payload = message.payload; + if (message.type === "ESIGN_RESPONSE" && payload?.partner_id) { + const partner = this.pos.db.get_partner_by_id(payload.partner_id); + if (partner) { + if ( + this.pos.waiting_for_esign_partner && + this.pos.waiting_for_esign_partner.id === partner.id + ) { + this.pos.waiting_for_esign_partner = null; + } + + partner.sign_attachment_id = payload.attachment_id; + + // Updates screen in general + window.dispatchEvent(new Event("resize")); + } + } + }, +}); diff --git a/pos_esign_request/static/src/overrides/models/pos_store.js b/pos_esign_request/static/src/overrides/models/pos_store.js new file mode 100644 index 0000000..1b0104f --- /dev/null +++ b/pos_esign_request/static/src/overrides/models/pos_store.js @@ -0,0 +1,14 @@ +/** @odoo-module */ + +import {PosStore} from "@point_of_sale/app/store/pos_store"; +import {patch} from "@web/core/utils/patch"; + +patch(PosStore.prototype, { + async requestSign(partner) { + this.waiting_for_esign_partner = partner; + await this.orm.call("pos.config", "sign_request", [this.config.id], { + partner_id: partner.id, + partner_name: partner.name, + }); + }, +}); diff --git a/pos_esign_request/static/src/overrides/partner_list/partner_line/partner_line.xml b/pos_esign_request/static/src/overrides/partner_list/partner_line/partner_line.xml new file mode 100644 index 0000000..865aa48 --- /dev/null +++ b/pos_esign_request/static/src/overrides/partner_list/partner_line/partner_line.xml @@ -0,0 +1,26 @@ + + + + + + + Waiting... + + + + + + + + + diff --git a/pos_esign_request/static/src/overrides/partner_list/partner_list.xml b/pos_esign_request/static/src/overrides/partner_list/partner_list.xml new file mode 100644 index 0000000..cbdda03 --- /dev/null +++ b/pos_esign_request/static/src/overrides/partner_list/partner_list.xml @@ -0,0 +1,20 @@ + + + + + E-Sign + + + !partner.sign_attachment_id and pos.waiting_for_esign_partner == partner + () => pos.requestSign(partner) + + + diff --git a/pos_esign_request/static/src/overrides/payment_screen/payment_screen.js b/pos_esign_request/static/src/overrides/payment_screen/payment_screen.js new file mode 100644 index 0000000..9c66b08 --- /dev/null +++ b/pos_esign_request/static/src/overrides/payment_screen/payment_screen.js @@ -0,0 +1,35 @@ +/** @odoo-module */ + +import {ErrorPopup} from "@point_of_sale/app/errors/popups/error_popup"; +import {PaymentScreen} from "@point_of_sale/app/screens/payment_screen/payment_screen"; +import {_t} from "@web/core/l10n/translation"; +import {patch} from "@web/core/utils/patch"; + +patch(PaymentScreen.prototype, { + async validateOrder() { + const partner = this.currentOrder.get_partner(); + + do { + // Customer is not set + if (!partner) break; + + // Does not need to ask for sign + if (!this.pos.config.ask_for_sign) break; + + // Customer did sign + if (partner.sign_attachment_id) break; + + // Sign is not mandatory + if (!this.pos.config.mandatory_ask_for_sign) break; + + // Customer did not sign and it is mandatory + this.popup.add(ErrorPopup, { + title: _t("Error"), + body: _t("Customer hasn't signed yet."), + }); + return false; + } while (false); // eslint-disable-line no-constant-condition + + return await super.validateOrder(...arguments); + }, +}); diff --git a/pos_esign_request/static/src/overrides/payment_screen/payment_screen.xml b/pos_esign_request/static/src/overrides/payment_screen/payment_screen.xml new file mode 100644 index 0000000..531f7db --- /dev/null +++ b/pos_esign_request/static/src/overrides/payment_screen/payment_screen.xml @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/pos_esign_request/static/src/xml/est_templates.xml b/pos_esign_request/static/src/xml/est_templates.xml deleted file mode 100644 index 1a0d65a..0000000 --- a/pos_esign_request/static/src/xml/est_templates.xml +++ /dev/null @@ -1,73 +0,0 @@ - - diff --git a/pos_esign_request/static/src/xml/pos_esign.xml b/pos_esign_request/static/src/xml/pos_esign.xml deleted file mode 100644 index ce5a413..0000000 --- a/pos_esign_request/static/src/xml/pos_esign.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - -
- - - E-Sign -
-
- - - - - - - - canCustomerValidatePayment - - - - - - E-Sign - - - - - - - - - - - - - Waiting... - - - - - -
diff --git a/pos_esign_request/views/assets.xml b/pos_esign_request/views/assets.xml deleted file mode 100644 index fb56cbd..0000000 --- a/pos_esign_request/views/assets.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - -