From e4879e7111c5f6ed424e23b3c10c55741a64cb48 Mon Sep 17 00:00:00 2001 From: Ivan Yelizariev Date: Mon, 10 Feb 2020 14:17:47 +0000 Subject: [PATCH 01/11] :green_heart: pre-commit: auto cleanups --- pos_disable_payment/__manifest__.py | 18 +++++++ pos_disable_payment/models.py | 19 +++++++ .../static/description/index.html | 51 +++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 pos_disable_payment/__manifest__.py create mode 100644 pos_disable_payment/models.py create mode 100644 pos_disable_payment/static/description/index.html diff --git a/pos_disable_payment/__manifest__.py b/pos_disable_payment/__manifest__.py new file mode 100644 index 0000000000..b1ffa54a66 --- /dev/null +++ b/pos_disable_payment/__manifest__.py @@ -0,0 +1,18 @@ +{ + "name": "Disable payments in POS", + "summary": "Control access to the POS payments", + "version": "11.0.3.7.0", + "author": "IT-Projects LLC, Ivan Yelizariev", + "license": "LGPL-3", + "category": "Point Of Sale", + "live_test_url": "http://apps.it-projects.info/shop/product/pos-multi-session?version=11.0", + "support": "pos@it-projects.info", + "website": "https://yelizariev.github.io", + "depends": ["point_of_sale"], + "images": ["images/pos_payment_access.png"], + "price": 40.00, + "currency": "EUR", + "data": ["views.xml"], + "demo": ["views/assets_demo.xml"], + "installable": True, +} diff --git a/pos_disable_payment/models.py b/pos_disable_payment/models.py new file mode 100644 index 0000000000..d0832f99fb --- /dev/null +++ b/pos_disable_payment/models.py @@ -0,0 +1,19 @@ +from odoo import fields, models + + +class ResUsers(models.Model): + _inherit = "res.users" + + allow_payments = fields.Boolean("Allow payments", default=True) + allow_delete_order = fields.Boolean("Allow remove non-empty order", default=True) + allow_discount = fields.Boolean("Allow discount", default=True) + allow_edit_price = fields.Boolean("Allow edit price", default=True) + allow_decrease_amount = fields.Boolean( + "Allow decrease quantity on order line", default=True + ) + allow_delete_order_line = fields.Boolean("Allow remove order line", default=True) + allow_create_order_line = fields.Boolean("Allow create order line", default=True) + allow_refund = fields.Boolean("Allow refunds", default=True) + allow_manual_customer_selecting = fields.Boolean( + "Allow manual customer selecting", default=True + ) diff --git a/pos_disable_payment/static/description/index.html b/pos_disable_payment/static/description/index.html new file mode 100644 index 0000000000..9edde74932 --- /dev/null +++ b/pos_disable_payment/static/description/index.html @@ -0,0 +1,51 @@ +
+
+
+

POS Disable Payment

+

Disable payments in POS

+

+ The module allows to disable payments in POS and in addition set up the following options: +

    +
  • Disable create order line
  • +
  • Disable removing non-empty order
  • +
  • Disable discount button
  • +
  • Disable edit price button
  • +
  • Disable decrease order line
  • +
  • Disable removing order line
  • +
  • Disable refunds
  • +
  • Disable customer selection
  • +
+ This could be useful when using POS only to get price of item by barcode. + Also it could help to solve the problem related to the theft of money by staff members (e.g., the situation is possible when employee can print the bill, take money, then delete all lines and restart a new order on the same order). +

+

+ To use go to Settings → Users section, open user edit form and move to "Point of Sale" tab. +

+
+ +
+
+
+
+ +
+
+

Free Support

+

You will get free support and assistance in case of any issues

+
+
+ +
+
+
+

Need our service?

+

Contact us by email or fill out request form

+ +
+
+
From f45cb2369f2aa352f87a66a0d257a3a414e3d5da Mon Sep 17 00:00:00 2001 From: Ivan Yelizariev Date: Wed, 12 Feb 2020 09:33:25 +0000 Subject: [PATCH 02/11] :green_heart: pre-commit: auto cleanups --- .../static/src/js/pos_disable_payment.js | 311 ++++++++++++++++++ pos_disable_payment/static/src/js/tour.js | 89 +++++ pos_disable_payment/views.xml | 55 ++++ pos_disable_payment/views/assets_demo.xml | 17 + 4 files changed, 472 insertions(+) create mode 100644 pos_disable_payment/static/src/js/pos_disable_payment.js create mode 100644 pos_disable_payment/static/src/js/tour.js create mode 100644 pos_disable_payment/views.xml create mode 100644 pos_disable_payment/views/assets_demo.xml diff --git a/pos_disable_payment/static/src/js/pos_disable_payment.js b/pos_disable_payment/static/src/js/pos_disable_payment.js new file mode 100644 index 0000000000..264255f848 --- /dev/null +++ b/pos_disable_payment/static/src/js/pos_disable_payment.js @@ -0,0 +1,311 @@ +odoo.define("pos_disable_payment", function(require) { + "use strict"; + + var chrome = require("point_of_sale.chrome"); + var screens = require("point_of_sale.screens"); + var core = require("web.core"); + var gui = require("point_of_sale.gui"); + var models = require("point_of_sale.models"); + var PosBaseWidget = require("point_of_sale.BaseWidget"); + var _t = core._t; + + models.load_fields("res.users", [ + "allow_payments", + "allow_delete_order", + "allow_discount", + "allow_edit_price", + "allow_decrease_amount", + "allow_delete_order_line", + "allow_create_order_line", + "allow_refund", + "allow_manual_customer_selecting", + ]); + + // Example of event binding and handling (triggering). Look up binding lower bind('change:cashier' ... + // Example extending of class (method set_cashier), than was created using extend. + // /odoo9/addons/point_of_sale/static/src/js/models.js + // exports.PosModel = Backbone.Model.extend ... + var PosModelSuper = models.PosModel; + models.PosModel = models.PosModel.extend({ + set_cashier: function() { + var old_cashier_id = this.db.get_cashier() && this.db.get_cashier().id; + PosModelSuper.prototype.set_cashier.apply(this, arguments); + if (old_cashier_id !== this.db.get_cashier().id) { + this.trigger("change:cashier", this); + } + }, + }); + + chrome.Chrome.include({ + init: function() { + this._super.apply(this, arguments); + this.pos.bind("change:selectedOrder", this.check_allow_delete_order, this); + this.pos.bind("change:cashier", this.check_allow_delete_order, this); + }, + check_allow_delete_order: function() { + var user = this.pos.get_cashier() || this.pos.user; + var order = this.pos.get_order(); + if (order) { + // User option calls "Allow remove non-empty order". So we got to check if its empty we can delete it. + if (!user.allow_delete_order && order.orderlines.length > 0) { + this.$(".deleteorder-button").addClass("disable"); + } else { + this.$(".deleteorder-button").removeClass("disable"); + } + } + }, + loading_hide: function() { + this._super(); + // Extra checks on init + this.check_allow_delete_order(); + }, + }); + chrome.OrderSelectorWidget.include({ + renderElement: function() { + this._super(); + this.chrome.check_allow_delete_order(); + }, + }); + + screens.OrderWidget.include({ + bind_order_events: function() { + this._super(); + var self = this; + var order = this.pos.get("selectedOrder"); + order.orderlines.bind( + "add remove", + this.chrome.check_allow_delete_order, + this.chrome + ); + this.pos.bind("change:cashier", function() { + self.check_numpad_access(); + }); + }, + orderline_change: function(line) { + this._super(line); + var user = this.pos.get_cashier() || this.pos.user; + if (line && line.quantity <= 0) { + if (user.allow_delete_order_line) { + $(".numpad-backspace").removeClass("disable"); + } else { + $(".numpad-backspace").addClass("disable"); + } + } else { + $(".numpad-backspace").removeClass("disable"); + } + this.check_numpad_access(line); + }, + click_line: function(orderline, event) { + this._super(orderline, event); + this.check_numpad_access(orderline); + }, + renderElement: function(scrollbottom) { + this._super(scrollbottom); + this.check_numpad_access(); + }, + check_numpad_access: function(line) { + var order = this.pos.get_order(); + if (order) { + line = line || order.get_selected_orderline(); + var user = this.pos.cashier || this.pos.user; + var state = this.getParent().numpad.state; + if (!line) { + $(".numpad") + .find(".numpad-backspace") + .removeClass("disable"); + $(".numpad") + .find("[data-mode='quantity']") + .removeClass("disable"); + return false; + } + + if (user.allow_decrease_amount) { + // Allow all buttons + if ( + $(".numpad") + .find("[data-mode='quantity']") + .hasClass("disable") + ) { + $(".numpad") + .find("[data-mode='quantity']") + .removeClass("disable"); + state.changeMode("quantity"); + } + if (user.allow_delete_order_line) { + $(".numpad") + .find(".numpad-backspace") + .removeClass("disable"); + } + } else { + // Disable the backspace button of numpad + $(".pads .numpad") + .find(".numpad-backspace") + .addClass("disable"); + } + } + }, + orderline_change_line: function(line) { + this._super(line); + var user = this.pos.cashier || this.pos.user; + var order = this.pos.get_order(); + if (order && !user.allow_decrease_amount) { + // Disable the backspace button of numpad + $(".pads .numpad") + .find(".numpad-backspace") + .addClass("disable"); + } + }, + }); + + // Here regular binding (in init) do not work for some reasons. We got to put binding method in renderElement. + screens.ProductScreenWidget.include({ + start: function() { + this._super(); + this.checkPayAllowed(); + this.checkCreateOrderLine(); + this.checkDiscountButton(); + }, + renderElement: function() { + this._super(); + this.pos.bind("change:cashier", this.checkPayAllowed, this); + this.pos.bind("change:cashier", this.checkCreateOrderLine, this); + this.pos.bind("change:cashier", this.checkDiscountButton, this); + }, + checkCreateOrderLine: function() { + var user = this.pos.get_cashier() || this.pos.user; + if (user.allow_create_order_line) { + $(".numpad").show(); + $(".rightpane").show(); + } else { + $(".numpad").hide(); + $(".rightpane").hide(); + } + }, + checkPayAllowed: function() { + var user = this.pos.get_cashier() || this.pos.user; + if (user.allow_payments) { + this.actionpad.$(".pay").removeClass("disable"); + } else { + this.actionpad.$(".pay").addClass("disable"); + } + }, + checkDiscountButton: function() { + var user = this.pos.get_cashier() || this.pos.user; + if (user.allow_discount) { + $(".control-button.js_discount").removeClass("disable"); + } else { + $(".control-button.js_discount").addClass("disable"); + } + }, + show: function(reset) { + this._super(reset); + if (reset) { + this.order_widget.check_numpad_access(); + } + }, + }); + screens.ScreenWidget.include({ + renderElement: function() { + this._super(); + var user = this.pos.get_cashier() || this.pos.user; + if (user.allow_payments) { + $(".pay").removeClass("disable"); + } else { + $(".pay").addClass("disable"); + } + if (user.allow_create_order_line) { + $(".numpad").show(); + $(".rightpane").show(); + } else { + $(".numpad").hide(); + $(".rightpane").hide(); + } + }, + }); + screens.ActionpadWidget.include({ + init: function(parent, options) { + var self = this; + this._super(parent, options); + this.pos.bind("change:cashier", this.checkManualCustomerSelecting, this); + }, + checkManualCustomerSelecting: function() { + var user = this.pos.get_cashier() || this.pos.user; + if (user.allow_manual_customer_selecting) { + this.$(".set-customer").removeClass("disable"); + } else { + this.$(".set-customer").addClass("disable"); + } + }, + renderElement: function() { + this._super(); + var user = this.pos.get_cashier() || this.pos.user; + if (user.allow_payments) { + $(".pay").removeClass("disable"); + } else { + $(".pay").addClass("disable"); + } + this.checkManualCustomerSelecting(); + }, + }); + screens.PaymentScreenWidget.include({ + init: function(parent, options) { + var self = this; + this._super(parent, options); + this.pos.bind("change:cashier", this.checkManualCustomerSelecting, this); + }, + checkManualCustomerSelecting: function() { + var user = this.pos.get_cashier() || this.pos.user; + if (user.allow_manual_customer_selecting) { + this.$(".js_set_customer").removeClass("disable"); + } else { + this.$(".js_set_customer").addClass("disable"); + } + }, + renderElement: function() { + this._super(); + this.checkManualCustomerSelecting(); + }, + }); + screens.NumpadWidget.include({ + init: function() { + this._super.apply(this, arguments); + this.pos.bind("change:cashier", this.check_access, this); + }, + renderElement: function() { + this._super(); + this.check_access(); + }, + check_access: function() { + var user = this.pos.get_cashier() || this.pos.user; + var order = this.pos.get_order(); + var orderline = false; + if (order) { + orderline = order.get_selected_orderline(); + } + if (user.allow_discount) { + this.$el.find("[data-mode='discount']").removeClass("disable"); + } else { + this.$el.find("[data-mode='discount']").addClass("disable"); + } + if (user.allow_edit_price) { + this.$el.find("[data-mode='price']").removeClass("disable"); + } else { + this.$el.find("[data-mode='price']").addClass("disable"); + } + if (user.allow_refund) { + this.$el.find(".numpad-minus").removeClass("disable"); + } else { + this.$el.find(".numpad-minus").addClass("disable"); + } + if (orderline && orderline.quantity <= 0) { + if (user.allow_delete_order_line) { + this.$el.find(".numpad-backspace").removeClass("disable"); + } else { + this.$el.find(".numpad-backspace").addClass("disable"); + } + } + }, + }); + + return screens; +}); diff --git a/pos_disable_payment/static/src/js/tour.js b/pos_disable_payment/static/src/js/tour.js new file mode 100644 index 0000000000..034de75995 --- /dev/null +++ b/pos_disable_payment/static/src/js/tour.js @@ -0,0 +1,89 @@ +odoo.define("pos_disable_payment.tour", function(require) { + "use strict"; + + var core = require("web.core"); + var tour = require("web_tour.tour"); + + tour.register( + "pos_disable_payment_tour", + { + test: true, + url: "/web", + }, + [ + { + trigger: '.o_app[data-menu="4"], .oe_menu_toggler[data-menu="4"]', + content: "Configuration options are available in the Settings app.", + position: "bottom", + }, + { + trigger: ".oe_menu_leaf[data-menu=58]", + content: "Open the Users menu", + position: "right", + }, + { + trigger: ".o_list_view > tbody > tr", + content: "Click on the item", + position: "bottom", + }, + { + trigger: ".o_form_button_edit", + content: "Click to edit user", + position: "bottom", + }, + { + trigger: '.nav-tabs a:contains("Point of Sale")', + content: "Move on Point of Sale tab", + position: "top", + }, + { + trigger: 'label:contains("Allow refunds")', + extra_trigger: + 'div[name="allow_refund"] input:propChecked({disabled: true})', + content: "Uncheck the box Allow refunds", + position: "right", + }, + { + trigger: ".o_form_button_save", + content: "Save the changes", + position: "bottom", + }, + { + trigger: + '.o_app[data-menu-xmlid="point_of_sale.menu_point_root"], .oe_menu_toggler[data-menu-xmlid="point_of_sale.menu_point_root"]', + content: + "Ready to launch your point of sale? Click here.", + position: "bottom", + }, + { + trigger: ".o_pos_kanban button.oe_kanban_action_button", + content: + "

Click to start the point of sale interface. It runs on tablets, laptops, or industrial hardware.

Once the session launched, the system continues to run without an internet connection.

", + position: "bottom", + }, + { + content: "Switch to table or make dummy action", + trigger: + ".table:not(.oe_invisible .neworder-button), .order-button.selected", + position: "bottom", + timeout: 20000, + }, + { + trigger: ".product-list .product", + content: "Add the first product", + position: "top", + }, + { + content: "Hidden", + trigger: ".pads", + run: function() { + if (!$(".numpad-minus").css("visibility") === "hidden") { + console.log("error", "The button is not hidden"); + } else { + console.log("Refunding feature is blocked as configured"); + } + }, + }, + ] + ); +}); diff --git a/pos_disable_payment/views.xml b/pos_disable_payment/views.xml new file mode 100644 index 0000000000..70111e91b7 --- /dev/null +++ b/pos_disable_payment/views.xml @@ -0,0 +1,55 @@ + + + +