diff --git a/.gitignore b/.gitignore index ef4d893..7db909f 100644 --- a/.gitignore +++ b/.gitignore @@ -132,4 +132,7 @@ dmypy.json .pyre/ tmp/ -.claude/settings.json +.claude/ +.vscode/ +.github +CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index b49667d..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,91 +0,0 @@ -# Kidekoop — Odoo 18.0 Custom Addons - -Odoo 18.0 (OCB community) addons repo. Code in **English**. - -## ⚠️ Critical rules (non-negotiable) - -- **DO NOT modify** the OCB core (`ocb/`) nor these original OCA addons (reference/inheritance only): - `product_main_seller`, `account_invoice_triple_discount`, `product_get_price_helper`, - `product_price_category`, `purchase_triple_discount`, `base_bank_from_iban`, `l10n_es_partner`. - All changes go in our own custom addons. -- **Do NOT use `_()` in field definitions** (triggers import-time warnings). Only in methods/executable code. -- **Business logic ALWAYS on `product.product`** (variants), never on `product.template` (use `related` there). - Avoids issues with pricelists and reports that operate at the variant level. -- **No logic in QWeb**: prepare all data in the Python controller; templates only do simple attribute access. -- **No business logic in JavaScript**: UI/events only. -- Translations: edit `.po` files with `polib` (append). **`msgmerge` corrupts the files** — do not use it. - -## Stack - -Odoo 18.0 · Python 3.10+ · PostgreSQL · Docker Compose · XML/QWeb/JS. -Formatting: `black` (line 88) + `isort` (black profile) + `flake8` + `pylint-odoo`. Pre-commit active. - -## Commands - -```bash -docker-compose up -d # env (8070=web, 8073=longpolling) -docker-compose run odoo odoo -d odoo --test-enable --stop-after-init -u # tests (run, NOT exec: avoids cache) -docker-compose exec odoo odoo -d odoo -u --stop-after-init # update addon -docker-compose logs -f odoo # logs -docker-compose exec db psql -U odoo -d odoo # postgres -make lint | make format | make check-format | make flake8 | make pylint-required -pre-commit run --all-files -``` - -## Odoo conventions - -- Models `snake.case` · classes `PascalCase` · files `snake_case.py` · XML IDs `module.name`. -- Manifest always `__manifest__.py`, version `18.0.X.Y.Z`, `depends` alphabetically ordered. -- Views: 4 spaces, explicit `position` in XPath, groups via `module.xml_id`, `sequence` for ordering. -- Bulk: prefer `search().write()` over loops; `create([vals, ...])` for bulk create. -- Notifications: `ir.actions.client` + `display_notification`. -- Detailed logging in price/discount computations: `_logger.info("[PRICE] ...", ...)`. - -## Custom addons - -Pricing & products: - -- `product_sale_price_from_pricelist` — auto-computes sale price from last purchase price + pricelist. - Requires taxes on the product and `last_purchase_price_compute_type != "manual_update"`. - Also applies supplier discounts from `supplierinfo`. -- `product_pricelist_total_margin` — total margin computed additively (Markup or Commercial Margin), with global limits. -- `product_price_category_supplier` — price categories per supplier. -- `product_origin_char` — free-text origin field per product (template-based). -- `account_invoice_triple_discount_readonly` — fix for triple-discount accumulation bug (always use it). - -Sales & website: - -- `website_sale_aplicoop` — Eskaera: collaborative purchasing for consumer co-ops (group orders, - per-member carts, cutoff/pickup dates, lazy loading, multi-language). -- `website_sale_disable_cart` — turns `/shop` into a read-only catalog: hides the cart UI and - redirects the standard `/shop/cart*` routes to a configurable URL (default `/shop`). -- `portal_event_registration` — portal users view their event registrations + upload attachments to chatter. - -Membership: - -- `membership_monthly_invoicing` — auto-creates a monthly membership invoice per active member (cron-driven). -- `membership_expiry_reminder` — automated renewal-reminder emails near expiry, with online renew link. - -Logistics & accounting: - -- `stock_picking_batch_custom` — batch picking extras: extra detailed-op columns, ordering by - category/product/partner, and a full-screen operator (Basket Assembly) view. -- `account_banking_mandate_batch` — contextual action to bulk-create/validate SEPA mandates for selected partners. -- `l10n_es_edi_tbai_reagyp_recibidas` — TicketBAI fix: REAGYP vendor-bill regime key 19 → 02. - -## Documentation - -- Per-addon details: its `README.rst` (OCA) or `README_DEV.md` (technical). -- Global patterns: `.github/copilot-instructions.md` (extended version of this file). -- Guides: `docs/QWEB_BEST_PRACTICES.md`, `docs/OCA_DOCUMENTATION.md`, `docs/LAZY_LOADING.md`. -- OCA `readme/` structure (DESCRIPTION, INSTALL, CONFIGURE, USAGE, CONTRIBUTORS, CREDITS). - Credits: Criptomart (author). - -## Commits - -`[TAG] module: description` — tags: `[ADD] [FIX] [IMP] [REF] [REM] [I18N] [DOC]`. - -## Available skills - -Per-language skills (`odoo-python`, `odoo-xml-views`, `odoo-qweb-html`, `odoo-javascript`) and the -OpenSpec workflow (`openspec-explore/propose/apply-change/archive-change`, `/opsx:*` commands) live in `.claude/`. 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 + diff --git a/pos_payment_method_cashdro_rounding/__init__.py b/pos_payment_method_cashdro_rounding/__init__.py new file mode 100644 index 0000000..9f3f569 --- /dev/null +++ b/pos_payment_method_cashdro_rounding/__init__.py @@ -0,0 +1,2 @@ +# Copyright Criptomart +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). diff --git a/pos_payment_method_cashdro_rounding/__manifest__.py b/pos_payment_method_cashdro_rounding/__manifest__.py new file mode 100644 index 0000000..a16b1e3 --- /dev/null +++ b/pos_payment_method_cashdro_rounding/__manifest__.py @@ -0,0 +1,18 @@ +{ + "name": "POS CashDro Rounding", + "version": "18.0.1.0.0", + "summary": "Apply cash/currency rounding to CashDro payment request amount.", + "category": "Point of Sale", + "license": "AGPL-3", + "author": "Criptomart", + "depends": [ + "point_of_sale", + "pos_payment_method_cashdro", + ], + "assets": { + "point_of_sale._assets_pos": [ + "pos_payment_method_cashdro_rounding/static/src/js/cashdro_rounding.esm.js", + ], + }, + "installable": True, +} diff --git a/pos_payment_method_cashdro_rounding/static/src/js/cashdro_rounding.esm.js b/pos_payment_method_cashdro_rounding/static/src/js/cashdro_rounding.esm.js new file mode 100644 index 0000000..eb01842 --- /dev/null +++ b/pos_payment_method_cashdro_rounding/static/src/js/cashdro_rounding.esm.js @@ -0,0 +1,37 @@ +/* Copyright Criptomart + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + + Apply the PoS cash rounding to the amount requested to the CashDro, keeping + the OCA integration flow untouched. +*/ + +import { PaymentCashdro } from "@pos_payment_method_cashdro/js/payment_cashdro.esm"; +import { patch } from "@web/core/utils/patch"; + +patch(PaymentCashdro.prototype, { + /** + * @override + * The base module asks the CashDro for the plain due amount, which ignores + * the cash rounding configured in the PoS. As the drawer can only give back + * change with the coins it holds, we request the amount the customer is + * really expected to pay: the due amount with the cash rounding applied, + * which is the very same amount core defaults a payment line to. + * + * Only the url builder is patched so the rest of the payment flow of the + * base module (request, acknowledge, polling and error handling) is kept. + */ + _cashdro_payment_url(parameters) { + const order = this.pos.get_order(); + const line = order.get_selected_paymentline(); + if (!line || !order.shouldRound(line.payment_method_id)) { + return super._cashdro_payment_url(...arguments); + } + // CashDro treats decimals as positions in an integer, so we round the + // product to avoid the trailing decimals of the floating point + // computation or the drawer would reject our request. + const amount = Math.round( + order.getDefaultAmountDueToPayIn(line.payment_method_id) * 100 + ); + return super._cashdro_payment_url({ ...parameters, amount }); + }, +});