diff --git a/pos_payment_method_cashdro/README.rst b/pos_payment_method_cashdro/README.rst index 3e490dea24..4bc22f5fe7 100644 --- a/pos_payment_method_cashdro/README.rst +++ b/pos_payment_method_cashdro/README.rst @@ -17,13 +17,13 @@ PoS Payment Method CashDro :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/14.0/pos_payment_method_cashdro + :target: https://github.com/OCA/pos/tree/16.0/pos_payment_method_cashdro :alt: OCA/pos .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/pos-14-0/pos-14-0-pos_payment_method_cashdro + :target: https://translation.odoo-community.org/projects/pos-16-0/pos-16-0-pos_payment_method_cashdro :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=14.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/pos&target_branch=16.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -69,14 +69,7 @@ With the proper configuration made: Known issues / Roadmap ====================== -* Cashdro terminals are designed to communicate in the local network, so they can't - receive or transmit any request to a remote Odoo server. So in order to implement - further features, la cash control or cash ins/outs it would be necessary to either: - - - Prepare the Cashdro terminal for a remote use (VPN, dns, etc.) and implement the - corresponding backend methods. - - Develope PoS frontend modules that allow to perform such operations and extend this - one making use of them. +* Integrate cash control (money inputs / outputs). Bug Tracker =========== @@ -84,7 +77,7 @@ 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 `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -120,6 +113,6 @@ 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. +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_payment_method_cashdro/__manifest__.py b/pos_payment_method_cashdro/__manifest__.py index 8d340aabcf..fb9e16b0d3 100644 --- a/pos_payment_method_cashdro/__manifest__.py +++ b/pos_payment_method_cashdro/__manifest__.py @@ -3,7 +3,7 @@ { "name": "PoS Payment Method CashDro", "summary": "Allows to pay with CashDro Terminals on the Point of Sale", - "version": "14.0.1.0.1", + "version": "16.0.1.0.0", "category": "Point Of Sale", "website": "https://github.com/OCA/pos", "author": "Tecnativa, Odoo Community Association (OCA)", @@ -12,8 +12,9 @@ "point_of_sale", ], "data": [ - "views/assets.xml", "views/pos_payment_method_views.xml", ], - "installable": True, + "assets": { + "point_of_sale.assets": ["pos_payment_method_cashdro/static/src/js/*.js"], + }, } diff --git a/pos_payment_method_cashdro/migrations/14.0.1.0.0/pre-migration.py b/pos_payment_method_cashdro/migrations/14.0.1.0.0/pre-migration.py deleted file mode 100644 index cf91241786..0000000000 --- a/pos_payment_method_cashdro/migrations/14.0.1.0.0/pre-migration.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright 2022 Tecnativa - David Vidal -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from openupgradelib import openupgrade - - -def move_cashdro_fields_to_new_model(env): - openupgrade.logged_query( - env.cr, - """ - ALTER TABLE pos_payment_method - ADD COLUMN cashdro_host varchar, - ADD COLUMN cashdro_user varchar, - ADD COLUMN cashdro_password varchar - """, - ) - openupgrade.logged_query( - env.cr, - """ - UPDATE pos_payment_method ppm SET - cashdro_host = aj.cashdro_host, - cashdro_user = aj.cashdro_user, - cashdro_password = aj.cashdro_password, - use_payment_terminal = 'cashdro' - FROM ( - SELECT * FROM account_journal - ) AS aj - WHERE ppm.name = aj.name AND aj.cashdro_payment_terminal - """, - ) - - -@openupgrade.migrate() -def migrate(env, version): - move_cashdro_fields_to_new_model(env) diff --git a/pos_payment_method_cashdro/models/__init__.py b/pos_payment_method_cashdro/models/__init__.py index 58690ef8d9..604d90cb18 100644 --- a/pos_payment_method_cashdro/models/__init__.py +++ b/pos_payment_method_cashdro/models/__init__.py @@ -1 +1,2 @@ from . import pos_payment_method +from . import pos_session diff --git a/pos_payment_method_cashdro/models/pos_payment_method.py b/pos_payment_method_cashdro/models/pos_payment_method.py index 80f9c96349..285984a90f 100644 --- a/pos_payment_method_cashdro/models/pos_payment_method.py +++ b/pos_payment_method_cashdro/models/pos_payment_method.py @@ -13,10 +13,23 @@ def _get_payment_terminal_selection(self): string="Cashdro Terminal Host Name or IP address", help="It must be reachable by the PoS in the store", ) - cashdro_user = fields.Char(string="Cashdro User") - cashdro_password = fields.Char(string="Cashdro Password") + cashdro_user = fields.Char() + cashdro_password = fields.Char() - def _onchange_is_cash_count(self): - if self.use_payment_terminal == "cashdro": - return - return super()._onchange_is_cash_count() + def _onchange_journal_id(self): + """Cash payment method force the `use_payment_terminal` to `False` as + it's assumed that a cash journal can't have a payment terminal. Let's keep + the method when it's needed""" + res = super()._onchange_journal_id() + if self.use_payment_terminal != "cashdro" and not self.is_cash_count: + return res + self.use_payment_terminal = "cashdro" + + def _compute_hide_use_payment_terminal(self): + """Now that we have the option to choose a payment terminal for the cashdro + payments, we can show the terminal options for cash payment types.""" + cash_payment_types = self.filtered(lambda x: x.type == "cash") + cash_payment_types.hide_use_payment_terminal = False + return super( + PosPaymentMethod, self - cash_payment_types + )._compute_hide_use_payment_terminal() diff --git a/pos_payment_method_cashdro/models/pos_session.py b/pos_payment_method_cashdro/models/pos_session.py new file mode 100644 index 0000000000..6123090956 --- /dev/null +++ b/pos_payment_method_cashdro/models/pos_session.py @@ -0,0 +1,14 @@ +# Copyright 2024 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import models + + +class PosSession(models.Model): + _inherit = "pos.session" + + def _loader_params_pos_payment_method(self): + result = super()._loader_params_pos_payment_method() + result["search_params"]["fields"].extend( + ["cashdro_host", "cashdro_user", "cashdro_password"] + ) + return result diff --git a/pos_payment_method_cashdro/readme/ROADMAP.rst b/pos_payment_method_cashdro/readme/ROADMAP.rst index 41a1ddb7bd..6b2b7fbed7 100644 --- a/pos_payment_method_cashdro/readme/ROADMAP.rst +++ b/pos_payment_method_cashdro/readme/ROADMAP.rst @@ -1,8 +1 @@ -* Cashdro terminals are designed to communicate in the local network, so they can't - receive or transmit any request to a remote Odoo server. So in order to implement - further features, la cash control or cash ins/outs it would be necessary to either: - - - Prepare the Cashdro terminal for a remote use (VPN, dns, etc.) and implement the - corresponding backend methods. - - Develope PoS frontend modules that allow to perform such operations and extend this - one making use of them. +* Integrate cash control (money inputs / outputs). \ No newline at end of file diff --git a/pos_payment_method_cashdro/static/description/index.html b/pos_payment_method_cashdro/static/description/index.html index 03ed753a22..42016157ef 100644 --- a/pos_payment_method_cashdro/static/description/index.html +++ b/pos_payment_method_cashdro/static/description/index.html @@ -1,4 +1,3 @@ - @@ -369,7 +368,7 @@

PoS Payment Method CashDro

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:e4f16039547a5076c72bc8bac4fe707fc850c042c66e965d9b89a1aacb16b138 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

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

+

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

This module allows to make payments with a Cashdro (https://www.cashdro.com) terminal on the Point of Sale frontend.

Table of contents

@@ -419,15 +418,7 @@

Usage

Known issues / Roadmap

    -
  • Cashdro terminals are designed to communicate in the local network, so they can’t -receive or transmit any request to a remote Odoo server. So in order to implement -further features, la cash control or cash ins/outs it would be necessary to either:
      -
    • Prepare the Cashdro terminal for a remote use (VPN, dns, etc.) and implement the -corresponding backend methods.
    • -
    • Develope PoS frontend modules that allow to perform such operations and extend this -one making use of them.
    • -
    -
  • +
  • Integrate cash control (money inputs / outputs).
@@ -435,7 +426,7 @@

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.

+feedback.

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

@@ -466,7 +457,7 @@

Maintainers

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.

+

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_payment_method_cashdro/static/src/js/models.esm.js b/pos_payment_method_cashdro/static/src/js/models.esm.js new file mode 100644 index 0000000000..1158d7f9c3 --- /dev/null +++ b/pos_payment_method_cashdro/static/src/js/models.esm.js @@ -0,0 +1,35 @@ +/** @odoo-module */ +/* Copyright 2021 Tecnativa - David Vidal + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).*/ +import {Order, register_payment_method} from "point_of_sale.models"; +import {PaymentCashdro} from "./payment_cashdro.esm"; +import Registries from "point_of_sale.Registries"; + +register_payment_method("cashdro", PaymentCashdro); + +const PaymentCashdroOrder = (OriginalOrder) => + class extends OriginalOrder { + constructor() { + super(...arguments); + this.in_cashdro_transaction = false; + } + /** + * @override + * Set the amount to 0 as it's going to be filled by the Cashdro response + */ + add_paymentline() { + const line = super.add_paymentline(...arguments); + if (!line) { + return line; + } + if ( + line.payment_method && + line.payment_method.use_payment_terminal === "cashdro" + ) { + line.set_amount(0); + } + return line; + } + }; + +Registries.Model.extend(Order, PaymentCashdroOrder); diff --git a/pos_payment_method_cashdro/static/src/js/models.js b/pos_payment_method_cashdro/static/src/js/models.js deleted file mode 100644 index d7481515b0..0000000000 --- a/pos_payment_method_cashdro/static/src/js/models.js +++ /dev/null @@ -1,37 +0,0 @@ -/* Copyright 2021-22 Tecnativa - David Vidal - License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).*/ -odoo.define("pos_payment_method_cashdro.models", function (require) { - "use strict"; - - const models = require("point_of_sale.models"); - const PaymentCashdro = require("pos_payment_method_cashdro.payment"); - - models.register_payment_method("cashdro", PaymentCashdro); - models.load_fields("pos.payment.method", [ - "cashdro_host", - "cashdro_user", - "cashdro_password", - ]); - - const order_super = models.Order.prototype; - - models.Order = models.Order.extend({ - initialize: function () { - order_super.initialize.apply(this, arguments); - this.in_cashdro_transaction = false; - }, - add_paymentline: function () { - const line = order_super.add_paymentline.apply(this, arguments); - if (!line) { - return line; - } - if ( - line.payment_method && - line.payment_method.use_payment_terminal === "cashdro" - ) { - line.set_amount(0); - } - return line; - }, - }); -}); diff --git a/pos_payment_method_cashdro/static/src/js/payment_cashdro.esm.js b/pos_payment_method_cashdro/static/src/js/payment_cashdro.esm.js new file mode 100644 index 0000000000..aca29ddfd1 --- /dev/null +++ b/pos_payment_method_cashdro/static/src/js/payment_cashdro.esm.js @@ -0,0 +1,197 @@ +/** @odoo-module */ +/* Copyright 2021 Tecnativa - David Vidal + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).*/ +import PaymentInterface from "point_of_sale.PaymentInterface"; + +export const PaymentCashdro = PaymentInterface.extend({ + /** + * @override + */ + init: function () { + this._super(...arguments); + this.enable_reversals(); + }, + + /** + * @override + */ + send_payment_reversal: function () { + this._super.apply(...arguments); + const order = this.pos.get_order(); + const line = order.selected_paymentline; + line.set_payment_status("reversing"); + return this.cashdro_send_payment_request(order); + }, + + /** + * @override + */ + send_payment_cancel: function () { + this._super(...arguments); + const operation = this.pos.get_order().cashdro_operation; + if (!operation) { + return Promise.resolve(); + } + return this.cashdro_finish_operation(operation); + }, + + /** + * @override + */ + send_payment_request: function () { + this._super(...arguments); + const order = this.pos.get_order(); + const line = order.selected_paymentline; + line.set_payment_status("waiting"); + return this.cashdro_send_payment_request(order); + }, + + // -------------------------------------------------------------------------- + // Private + // -------------------------------------------------------------------------- + + cashdro_send_payment_request: async function (order) { + // The payment is done in three concatenated steps: + // 1. The POS send a payment request, to which the Cashdro respondes + // with an operation id. + // 2. Then the POS has to acknowledge that such operation id has + // been received. + // 3. Once acknowledged the POS has to send a payment request to the + // cashdro. Once the Cashdro responses with a "F" state (for + // finished) we'll get the response and fill the tendered money + // for the payment line. + const payment_line = order.selected_paymentline; + try { + // Cashdro treats decimals as positions in an integer we also have + // to deal with floating point computing to avoid decimals at the + // end or the drawer will reject our request. + const amount = Math.round(order.get_due(payment_line) * 100); + const res = await this._cashdro_request( + this._cashdro_payment_url({amount: amount}) + ); + // It comes handy to log the response from the drawer, as + // we can diagnose the right sytmoms for each issue + console.log(res); + const operation_id = res.data || ""; + this.pos.get_order().cashdro_operation = operation_id; + // Acknowledge the operation + var ack_url = this._cashdro_ack_url(operation_id); + const res_ack = await this._cashdro_request(ack_url); + // Validate the operation + console.log(res_ack); + var ask_url = this._cashdro_ask_url(operation_id); + const operation_data = await this._cashdro_request_payment(ask_url); + // This might be too verbose, but it helps a lot to diagnose issues and + // their reasons. + console.log(operation_data); + var data = JSON.parse(operation_data.data); + payment_line.cashdro_operation_data = data; + var tendered = data.operation.totalin / 100; + payment_line.set_amount(tendered); + } catch (error) { + // We wan't to be able to retry after any error. + // TODO: catch specific exceptions + payment_line.set_payment_status("retry"); + throw error; + } + return true; + }, + + cashdro_finish_operation: async function (operation) { + // Finish the Cashdro running operation + var order = this.pos.get_order(); + if (operation) { + await this._cashdro_request(this._cashdro_finish_url(operation)); + order.cashdro_operation = false; + } + }, + + // API communication methods + + _cashdro_url: function () { + // Cashdro machines don't support safe POST calls, so we're sending + // all the data quite unsafely constantly... + const method = this.pos.get_order().selected_paymentline.payment_method; + const host = method && method.cashdro_host; + if (!host) { + return false; + } + let url = `https://${host}/Cashdro3WS/index.php`; + url += `?name=${method.cashdro_user}`; + url += `&password=${method.cashdro_password}`; + return url; + }, + + _cashdro_payment_url: function (parameters) { + // Compose the url for a sale report to Cashdro + const user = this.pos.get_cashier().id || this.pos.user.id; + let url = `${this._cashdro_url()}&operation=startOperation&type=4`; + url += `&posid=pos-${this.pos.pos_session.name}`; + url += `&posuser=${user}`; + url += `¶meters=${encodeURIComponent(JSON.stringify(parameters))}`; + return url; + }, + + _cashdro_ack_url: function (operation_id) { + // Compose the url for a sale report to Cashdro + var url = this._cashdro_url(); + url += "&operation=acknowledgeOperationId"; + url += "&operationId=" + operation_id; + return url; + }, + + _cashdro_ask_url: function (operation_id) { + // Compose the url for to report a sale to Cashdro + var url = this._cashdro_url(); + url += "&operation=askOperation"; + url += "&operationId=" + operation_id; + return url; + }, + + _cashdro_finish_url: function (operation_id) { + // Compose the url for a sale report to Cashdro + var url = this._cashdro_url(); + url += "&operation=finishOperation&type=2"; + url += "&operationId=" + operation_id; + return url; + }, + + _cashdro_request: function (url) { + // We'll use it for regular requests + return $.ajax({ + url: url, + method: "GET", + async: true, + success: function (response) { + return response; + }, + }); + }, + + /** + * This is a special request, as we keep requesting the CashDro until we get + * the *finished* state that will give us the amount received in the cashdrawer. + * + * @param {String} request_url + * @returns promise + */ + _cashdro_request_payment: function (request_url) { + var def = $.Deferred(); + var _request_payment = (url) => { + $.ajax({ + url: url, + method: "GET", + success: (response) => { + var data = JSON.parse(response.data); + if (data.operation.state === "F") { + def.resolve(response); + } else { + _request_payment(url); + } + }, + }); + }; + _request_payment(request_url); + return def; + }, +}); diff --git a/pos_payment_method_cashdro/static/src/js/payment_cashdro.js b/pos_payment_method_cashdro/static/src/js/payment_cashdro.js deleted file mode 100644 index 0c552744ad..0000000000 --- a/pos_payment_method_cashdro/static/src/js/payment_cashdro.js +++ /dev/null @@ -1,176 +0,0 @@ -/* Copyright 2021-22 Tecnativa - David Vidal - License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).*/ -odoo.define("pos_payment_method_cashdro.payment", function (require) { - "use strict"; - - var PaymentInterface = require("point_of_sale.PaymentInterface"); - - const PaymentCashdro = PaymentInterface.extend({ - /** - * @override - */ - send_payment_cancel: function () { - this._super(...arguments); - const operation = this.pos.get_order().cashdro_operation; - if (!operation) { - return Promise.resolve(); - } - return this.cashdro_finish_operation(operation); - }, - - /** - * @override - */ - send_payment_request: function () { - this._super(...arguments); - const order = this.pos.get_order(); - const line = order.selected_paymentline; - line.set_payment_status("waiting"); - return this.cashdro_send_payment_request(order); - }, - - // -------------------------------------------------------------------------- - // Private - // -------------------------------------------------------------------------- - - cashdro_send_payment_request: async function (order) { - // The payment is done in three concatenated steps: - // 1. The POS send a payment request, to which the Cashdro respondes - // with an operation id. - // 2. Then the POS has to acknowledge that such operation id has - // been received. - // 3. Once acknowledged the POS has to send a payment request to the - // cashdro. Once the Cashdro responses with a "F" state (for - // finished) we'll get the response and fill the tendered money - // for the payment line. - const payment_line = order.selected_paymentline; - // Cashdro treats decimals as positions in an integer we also have - // to deal with floating point computing to avoid decimals at the - // end or the drawer will reject our request. - const amount = Math.round(order.get_due(payment_line) * 100); - const res = await this._cashdro_request( - this._cashdro_payment_url({amount: amount}) - ); - // It comes handy to log the response from the drawer, as - // we can diagnose the right sytmoms for each issue - console.log(res); - const operation_id = res.data || ""; - this.pos.get_order().cashdro_operation = operation_id; - // Acknowledge the operation - var ack_url = this._cashdro_ack_url(operation_id); - const res_ack = await this._cashdro_request(ack_url); - // Validate the operation - console.log(res_ack); - var ask_url = this._cashdro_ask_url(operation_id); - const operation_data = await this._cashdro_request_payment(ask_url); - // This might be too verbose, but it helps a lot to diagnose issues and - // their reasons. - console.log(operation_data); - var data = JSON.parse(operation_data.data); - payment_line.cashdro_operation_data = data; - var tendered = data.operation.totalin / 100; - payment_line.set_amount(tendered); - return true; - }, - - cashdro_finish_operation: async function (operation) { - // Finish the Cashdro running operation - var order = this.pos.get_order(); - if (operation) { - await this._cashdro_request(this._cashdro_finish_url(operation)); - order.cashdro_operation = false; - } - }, - - // API communication methods - - _cashdro_url: function () { - // Cashdro machines don't support safe POST calls, so we're sending - // all the data quite unsafely constantly... - const method = this.pos.get_order().selected_paymentline.payment_method; - const host = method && method.cashdro_host; - if (!host) { - return false; - } - let url = `https://${host}/Cashdro3WS/index.php`; - url += `?name=${method.cashdro_user}`; - url += `&password=${method.cashdro_password}`; - return url; - }, - - _cashdro_payment_url: function (parameters) { - // Compose the url for a sale report to Cashdro - const user = this.pos.get_cashier().id || this.pos.user.id; - let url = `${this._cashdro_url()}&operation=startOperation&type=4`; - url += `&posid=pos-${this.pos.pos_session.name}`; - url += `&posuser=${user}`; - url += `¶meters=${JSON.stringify(parameters)}`; - return url; - }, - - _cashdro_ack_url: function (operation_id) { - // Compose the url for a sale report to Cashdro - var url = this._cashdro_url(); - url += "&operation=acknowledgeOperationId"; - url += "&operationId=" + operation_id; - return url; - }, - - _cashdro_ask_url: function (operation_id) { - // Compose the url for to report a sale to Cashdro - var url = this._cashdro_url(); - url += "&operation=askOperation"; - url += "&operationId=" + operation_id; - return url; - }, - - _cashdro_finish_url: function (operation_id) { - // Compose the url for a sale report to Cashdro - var url = this._cashdro_url(); - url += "&operation=finishOperation&type=2"; - url += "&operationId=" + operation_id; - return url; - }, - - _cashdro_request: function (url) { - // We'll use it for regular requests - return $.ajax({ - url: url, - method: "GET", - async: true, - success: function (response) { - return response; - }, - }); - }, - - /** - * This is a special request, as we keep requesting the CashDro until we get - * the *finished* state that will give us the amount received in the cashdrawer. - * - * @param {String} request_url - * @returns promise - */ - _cashdro_request_payment: function (request_url) { - var def = $.Deferred(); - var _request_payment = (url) => { - $.ajax({ - url: url, - method: "GET", - success: (response) => { - var data = JSON.parse(response.data); - if (data.operation.state === "F") { - def.resolve(response); - } else { - _request_payment(url); - } - }, - }); - }; - _request_payment(request_url); - return def; - }, - }); - - return PaymentCashdro; -}); diff --git a/pos_payment_method_cashdro/views/assets.xml b/pos_payment_method_cashdro/views/assets.xml deleted file mode 100644 index a60949bdf0..0000000000 --- a/pos_payment_method_cashdro/views/assets.xml +++ /dev/null @@ -1,15 +0,0 @@ - - -