diff --git a/pos_payment_method_cashdro_fix/README.rst b/pos_payment_method_cashdro_fix/README.rst new file mode 100644 index 0000000..9854e0a --- /dev/null +++ b/pos_payment_method_cashdro_fix/README.rst @@ -0,0 +1,86 @@ +====================== +POS CashDro 18.0 Fixes +====================== + +.. |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 + +|badge1| |badge2| + +The 18.0 migration of ``pos_payment_method_cashdro`` +(`OCA/pos@6a4c251d `_) left three +uses of the 17.0 API that stop the CashDro integration from working. The module +has no tests nor tours and needs a physical drawer to be exercised, so nothing +in the OCA CI catches them. + +This module patches the three of them from the outside, without touching the +OCA sources: + +#. ``_cashdro_url()`` reads ``order.selected_paymentline``, replaced in 18.0 by + ``order.get_selected_paymentline()``. Reading ``.payment_method`` on the + resulting ``undefined`` raises a ``TypeError`` on every payment, which the + caller swallows into a generic *"An error occurred while connecting to the + cashdro"* message. +#. The ``add_paymentline`` patch checks ``line.payment_method``, renamed to + ``line.payment_method_id`` in 18.0. The condition is never true, so the + payment line keeps the default due amount instead of waiting for the amount + the customer inserts in the drawer. +#. ``pos.session._loader_params_pos_payment_method`` is the 17.0 loading API, + replaced in 18.0 by ``_load_pos_data_fields`` on each model. The override is + dead code, so ``cashdro_host``, ``cashdro_user`` and ``cashdro_password`` + never reach the front end. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +There is nothing to configure: installing the module is enough. + +To check the three fixes on a PoS with a CashDro terminal: + +#. Open a PoS session, add a product and go to the payment screen. +#. Select the CashDro payment method. The payment line must show **0**, not the + due amount (fix 2). +#. The drawer must start the operation and ask for the amount, instead of the + *"An error occurred while connecting to the cashdro"* dialog (fixes 1 and 3). +#. With no CashDro at hand, the browser console tells fix 1 from fix 3 apart: + without this module the failure is a ``TypeError: Cannot read properties of + undefined (reading 'payment_method')``; with it, a network error against the + configured host. + +The Python side is covered by the tests. The base module lives outside this +repo, so the OCA sources have to be on the addons path (see +``docker-compose.override.yml``, which is local and untracked):: + + docker-compose run --rm odoo odoo -c /etc/odoo/odoo.conf \ + --addons-path=/mnt/extra-addons,/mnt/oca/pos,/usr/lib/python3/dist-packages/odoo/addons \ + -d odoo -u pos_payment_method_cashdro_fix --test-enable --stop-after-init + +Known issues / Roadmap +====================== + +This module is meant to be temporary. The fixes are proposed upstream in +``upstream/pos_payment_method_cashdro-18.0-fixes.patch``; once they are merged +in ``OCA/pos`` this module can be uninstalled and removed. + +It is written to stay harmless in the meantime: the Python override only adds +the credential fields that are missing from the loader, and the JavaScript +patches are idempotent with respect to the fixed upstream code. + +The JavaScript side can't be covered by automated tests, as it needs a CashDro +terminal answering on the network. + +Contributors +============ + +* `Criptomart `_: + + * Criptomart diff --git a/pos_payment_method_cashdro_fix/__init__.py b/pos_payment_method_cashdro_fix/__init__.py new file mode 100644 index 0000000..8f9e965 --- /dev/null +++ b/pos_payment_method_cashdro_fix/__init__.py @@ -0,0 +1,3 @@ +# Copyright 2026 - Today Criptomart +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from . import models diff --git a/pos_payment_method_cashdro_fix/__manifest__.py b/pos_payment_method_cashdro_fix/__manifest__.py new file mode 100644 index 0000000..bbe4598 --- /dev/null +++ b/pos_payment_method_cashdro_fix/__manifest__.py @@ -0,0 +1,23 @@ +# Copyright 2026 - Today Criptomart +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +{ + "name": "POS CashDro 18.0 Fixes", + "version": "18.0.1.0.0", + "summary": "Fix the 17.0 API leftovers that break pos_payment_method_cashdro on 18.0", + "category": "Point of Sale", + "license": "AGPL-3", + "author": "Criptomart", + "maintainers": ["Criptomart"], + "website": "https://git.criptomart.net/criptomart/addons-cm", + "depends": [ + "point_of_sale", + "pos_payment_method_cashdro", + ], + "assets": { + "point_of_sale._assets_pos": [ + "pos_payment_method_cashdro_fix/static/src/js/payment_cashdro_fix.esm.js", + "pos_payment_method_cashdro_fix/static/src/js/models_fix.esm.js", + ], + }, + "installable": True, +} diff --git a/pos_payment_method_cashdro_fix/models/__init__.py b/pos_payment_method_cashdro_fix/models/__init__.py new file mode 100644 index 0000000..58690ef --- /dev/null +++ b/pos_payment_method_cashdro_fix/models/__init__.py @@ -0,0 +1 @@ +from . import pos_payment_method diff --git a/pos_payment_method_cashdro_fix/models/pos_payment_method.py b/pos_payment_method_cashdro_fix/models/pos_payment_method.py new file mode 100644 index 0000000..8ec6d8d --- /dev/null +++ b/pos_payment_method_cashdro_fix/models/pos_payment_method.py @@ -0,0 +1,26 @@ +# Copyright 2026 - Today Criptomart +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import api +from odoo import models + +CASHDRO_FIELDS = ["cashdro_host", "cashdro_user", "cashdro_password"] + + +class PosPaymentMethod(models.Model): + _inherit = "pos.payment.method" + + @api.model + def _load_pos_data_fields(self, config_id): + """Send the CashDro credentials to the PoS front end. + + `pos_payment_method_cashdro` still declares them through + `_loader_params_pos_payment_method`, the 17.0 loading API, which 18.0 + replaced by this method. That override is dead code, so the front end + never receives the host and can't build the CashDro url. + + Only the missing fields are added, so this module stays harmless once + the base one is fixed upstream. + """ + fields = super()._load_pos_data_fields(config_id) + fields.extend(field for field in CASHDRO_FIELDS if field not in fields) + return fields diff --git a/pos_payment_method_cashdro_fix/readme/CONTRIBUTORS.rst b/pos_payment_method_cashdro_fix/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000..16df929 --- /dev/null +++ b/pos_payment_method_cashdro_fix/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* `Criptomart `_: + + * Criptomart diff --git a/pos_payment_method_cashdro_fix/readme/DESCRIPTION.rst b/pos_payment_method_cashdro_fix/readme/DESCRIPTION.rst new file mode 100644 index 0000000..c5f2de1 --- /dev/null +++ b/pos_payment_method_cashdro_fix/readme/DESCRIPTION.rst @@ -0,0 +1,22 @@ +The 18.0 migration of ``pos_payment_method_cashdro`` +(`OCA/pos@6a4c251d `_) left three +uses of the 17.0 API that stop the CashDro integration from working. The module +has no tests nor tours and needs a physical drawer to be exercised, so nothing +in the OCA CI catches them. + +This module patches the three of them from the outside, without touching the +OCA sources: + +#. ``_cashdro_url()`` reads ``order.selected_paymentline``, replaced in 18.0 by + ``order.get_selected_paymentline()``. Reading ``.payment_method`` on the + resulting ``undefined`` raises a ``TypeError`` on every payment, which the + caller swallows into a generic *"An error occurred while connecting to the + cashdro"* message. +#. The ``add_paymentline`` patch checks ``line.payment_method``, renamed to + ``line.payment_method_id`` in 18.0. The condition is never true, so the + payment line keeps the default due amount instead of waiting for the amount + the customer inserts in the drawer. +#. ``pos.session._loader_params_pos_payment_method`` is the 17.0 loading API, + replaced in 18.0 by ``_load_pos_data_fields`` on each model. The override is + dead code, so ``cashdro_host``, ``cashdro_user`` and ``cashdro_password`` + never reach the front end. diff --git a/pos_payment_method_cashdro_fix/readme/ROADMAP.rst b/pos_payment_method_cashdro_fix/readme/ROADMAP.rst new file mode 100644 index 0000000..bab1608 --- /dev/null +++ b/pos_payment_method_cashdro_fix/readme/ROADMAP.rst @@ -0,0 +1,10 @@ +This module is meant to be temporary. The fixes are proposed upstream in +``upstream/pos_payment_method_cashdro-18.0-fixes.patch``; once they are merged +in ``OCA/pos`` this module can be uninstalled and removed. + +It is written to stay harmless in the meantime: the Python override only adds +the credential fields that are missing from the loader, and the JavaScript +patches are idempotent with respect to the fixed upstream code. + +The JavaScript side can't be covered by automated tests, as it needs a CashDro +terminal answering on the network. See ``USAGE`` for the manual check. diff --git a/pos_payment_method_cashdro_fix/readme/USAGE.rst b/pos_payment_method_cashdro_fix/readme/USAGE.rst new file mode 100644 index 0000000..6499224 --- /dev/null +++ b/pos_payment_method_cashdro_fix/readme/USAGE.rst @@ -0,0 +1,21 @@ +There is nothing to configure: installing the module is enough. + +To check the three fixes on a PoS with a CashDro terminal: + +#. Open a PoS session, add a product and go to the payment screen. +#. Select the CashDro payment method. The payment line must show **0**, not the + due amount (fix 2). +#. The drawer must start the operation and ask for the amount, instead of the + *"An error occurred while connecting to the cashdro"* dialog (fixes 1 and 3). +#. With no CashDro at hand, the browser console tells fix 1 from fix 3 apart: + without this module the failure is a ``TypeError: Cannot read properties of + undefined (reading 'payment_method')``; with it, a network error against the + configured host. + +The Python side is covered by the tests. The base module lives outside this +repo, so the OCA sources have to be on the addons path (see +``docker-compose.override.yml``, which is local and untracked):: + + docker-compose run --rm odoo odoo -c /etc/odoo/odoo.conf \ + --addons-path=/mnt/extra-addons,/mnt/oca/pos,/usr/lib/python3/dist-packages/odoo/addons \ + -d odoo -u pos_payment_method_cashdro_fix --test-enable --stop-after-init diff --git a/pos_payment_method_cashdro_fix/static/src/js/models_fix.esm.js b/pos_payment_method_cashdro_fix/static/src/js/models_fix.esm.js new file mode 100644 index 0000000..51598ff --- /dev/null +++ b/pos_payment_method_cashdro_fix/static/src/js/models_fix.esm.js @@ -0,0 +1,31 @@ +/* Copyright 2026 - Today Criptomart + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + + The `add_paymentline` patch of `pos_payment_method_cashdro` checks + `line.payment_method`, renamed to `payment_method_id` in 18.0, so the + condition is never met and the line keeps the default due amount instead of + waiting for the amount the customer actually inserts in the drawer. +*/ + +import { PosOrder } from "@point_of_sale/app/models/pos_order"; +import { patch } from "@web/core/utils/patch"; + +patch(PosOrder.prototype, { + /** + * @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_id?.use_payment_terminal === "cashdro" && + line.amount > 0 // For refund transactions we need to keep the amount + ) { + line.set_amount(0); + } + return line; + }, +}); diff --git a/pos_payment_method_cashdro_fix/static/src/js/payment_cashdro_fix.esm.js b/pos_payment_method_cashdro_fix/static/src/js/payment_cashdro_fix.esm.js new file mode 100644 index 0000000..3410747 --- /dev/null +++ b/pos_payment_method_cashdro_fix/static/src/js/payment_cashdro_fix.esm.js @@ -0,0 +1,33 @@ +/* Copyright 2026 - Today Criptomart + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + + `_cashdro_url` was left with the 17.0 API in the 18.0 migration of + `pos_payment_method_cashdro`: `order.selected_paymentline` no longer exists, + so reading `.payment_method` on it throws a TypeError that the caller + swallows as a generic connection error. Rewritten with the 18.0 accessors. +*/ + +import { PaymentCashdro } from "@pos_payment_method_cashdro/js/payment_cashdro.esm"; +import { patch } from "@web/core/utils/patch"; + +patch(PaymentCashdro.prototype, { + /** + * @override + * The whole body has to be replaced: the very first statement is the one + * that breaks, so there is nothing left to delegate to `super`. + */ + _cashdro_url() { + // Cashdro machines don't support safe POST calls, so we're sending + // all the data quite unsafely constantly... + const order = this.pos.get_order(); + const method = order.get_selected_paymentline()?.payment_method_id; + const host = method && method.cashdro_host; + if (!host) { + return false; + } + let url = `${host}/Cashdro3WS/index.php`; + url += `?name=${method.cashdro_user}`; + url += `&password=${method.cashdro_password}`; + return url; + }, +}); diff --git a/pos_payment_method_cashdro_fix/tests/__init__.py b/pos_payment_method_cashdro_fix/tests/__init__.py new file mode 100644 index 0000000..02c4d2e --- /dev/null +++ b/pos_payment_method_cashdro_fix/tests/__init__.py @@ -0,0 +1 @@ +from . import test_load_pos_data_fields diff --git a/pos_payment_method_cashdro_fix/tests/test_load_pos_data_fields.py b/pos_payment_method_cashdro_fix/tests/test_load_pos_data_fields.py new file mode 100644 index 0000000..ebee5e4 --- /dev/null +++ b/pos_payment_method_cashdro_fix/tests/test_load_pos_data_fields.py @@ -0,0 +1,33 @@ +# Copyright 2026 - Today Criptomart +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests import tagged +from odoo.tests.common import TransactionCase + +from ..models.pos_payment_method import CASHDRO_FIELDS + + +@tagged("post_install", "-at_install") +class TestLoadPosDataFields(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.payment_method = cls.env["pos.payment.method"] + # Nothing dereferences it, but we pass a real config when there's one + cls.config_id = cls.env["pos.config"].search([], limit=1).id + + def test_cashdro_credentials_are_loaded(self): + """The front end can't build the CashDro url without them.""" + fields = self.payment_method._load_pos_data_fields(self.config_id) + for field in CASHDRO_FIELDS: + self.assertIn(field, fields) + + def test_no_duplicated_fields(self): + """Once the base module is fixed upstream we must not add them twice.""" + fields = self.payment_method._load_pos_data_fields(self.config_id) + self.assertEqual(sorted(fields), sorted(set(fields))) + + def test_fields_exist_on_the_model(self): + """Guard against a rename of the credentials in the base module.""" + for field in CASHDRO_FIELDS: + self.assertIn(field, self.payment_method._fields) diff --git a/pos_payment_method_cashdro_fix/upstream/pos_payment_method_cashdro-18.0-fixes.patch b/pos_payment_method_cashdro_fix/upstream/pos_payment_method_cashdro-18.0-fixes.patch new file mode 100644 index 0000000..84ca58f --- /dev/null +++ b/pos_payment_method_cashdro_fix/upstream/pos_payment_method_cashdro-18.0-fixes.patch @@ -0,0 +1,133 @@ +From 11c65e8000282b9a761346f1fbff16e09581fc17 Mon Sep 17 00:00:00 2001 +From: Criptomart +Date: Tue, 11 Aug 2026 13:21:28 +0200 +Subject: [PATCH] [FIX] pos_payment_method_cashdro: leftovers of the 17.0 API + +The 18.0 migration kept three uses of APIs that no longer exist, and the +module has neither tests nor tours (it needs a physical drawer), so the CI +can't catch them. + +* `_cashdro_url()` reads `order.selected_paymentline`, replaced by + `order.get_selected_paymentline()` in 18.0. Reading `.payment_method` on + the resulting `undefined` throws a TypeError on every payment, which + `cashdro_send_payment_request()` swallows into a generic "An error occurred + while connecting to the cashdro" dialog. + +* The `add_paymentline()` patch checks `line.payment_method`, renamed to + `line.payment_method_id` in 18.0, so the payment line is never zeroed and + keeps the default due amount instead of the amount the customer inserts. + +* `_loader_params_pos_payment_method()` is the 17.0 loading API, replaced in + 18.0 by `_load_pos_data_fields()` on each model. The override is dead code + and the CashDro credentials never reach the front end, so `_cashdro_url()` + has no host to build the url with. +--- + pos_payment_method_cashdro/__manifest__.py | 2 +- + pos_payment_method_cashdro/models/__init__.py | 1 - + .../models/pos_payment_method.py | 11 ++++++++++- + pos_payment_method_cashdro/models/pos_session.py | 14 -------------- + .../static/src/js/models.esm.js | 4 ++-- + .../static/src/js/payment_cashdro.esm.js | 3 ++- + 6 files changed, 15 insertions(+), 20 deletions(-) + delete mode 100644 pos_payment_method_cashdro/models/pos_session.py + +diff --git a/pos_payment_method_cashdro/__manifest__.py b/pos_payment_method_cashdro/__manifest__.py +index 708aac5..c19a642 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": "18.0.1.0.2", ++ "version": "18.0.1.0.3", + "category": "Point Of Sale", + "website": "https://github.com/OCA/pos", + "author": "Tecnativa, Odoo Community Association (OCA)", +diff --git a/pos_payment_method_cashdro/models/__init__.py b/pos_payment_method_cashdro/models/__init__.py +index 604d90c..58690ef 100644 +--- a/pos_payment_method_cashdro/models/__init__.py ++++ b/pos_payment_method_cashdro/models/__init__.py +@@ -1,2 +1 @@ + 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 285984a..08a64a1 100644 +--- a/pos_payment_method_cashdro/models/pos_payment_method.py ++++ b/pos_payment_method_cashdro/models/pos_payment_method.py +@@ -1,6 +1,6 @@ + # Copyright 2021 Tecnativa - David Vidal + # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +-from odoo import fields, models ++from odoo import api, fields, models + + + class PosPaymentMethod(models.Model): +@@ -16,6 +16,15 @@ class PosPaymentMethod(models.Model): + cashdro_user = fields.Char() + cashdro_password = fields.Char() + ++ @api.model ++ def _load_pos_data_fields(self, config_id): ++ """The front end needs the credentials to build the CashDro url""" ++ return super()._load_pos_data_fields(config_id) + [ ++ "cashdro_host", ++ "cashdro_user", ++ "cashdro_password", ++ ] ++ + 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 +diff --git a/pos_payment_method_cashdro/models/pos_session.py b/pos_payment_method_cashdro/models/pos_session.py +deleted file mode 100644 +index 6123090..0000000 +--- a/pos_payment_method_cashdro/models/pos_session.py ++++ /dev/null +@@ -1,14 +0,0 @@ +-# 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/static/src/js/models.esm.js b/pos_payment_method_cashdro/static/src/js/models.esm.js +index d228f08..d1ee2f7 100644 +--- a/pos_payment_method_cashdro/static/src/js/models.esm.js ++++ b/pos_payment_method_cashdro/static/src/js/models.esm.js +@@ -22,8 +22,8 @@ patch(PosOrder.prototype, { + return line; + } + if ( +- line.payment_method && +- line.payment_method.use_payment_terminal === "cashdro" && ++ line.payment_method_id && ++ line.payment_method_id.use_payment_terminal === "cashdro" && + line.amount > 0 // For refund transactions we need to keep the amount + ) { + line.set_amount(0); +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 +index 0c0bdf2..2c618bf 100644 +--- a/pos_payment_method_cashdro/static/src/js/payment_cashdro.esm.js ++++ b/pos_payment_method_cashdro/static/src/js/payment_cashdro.esm.js +@@ -107,7 +107,8 @@ export class PaymentCashdro extends PaymentInterface { + _cashdro_url() { + // 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 order = this.pos.get_order(); ++ const method = order.get_selected_paymentline()?.payment_method_id; + const host = method && method.cashdro_host; + if (!host) { + return false; +-- +2.43.0 +