Compare commits
No commits in common. "2b1cabbb4359a864220046cfc8fb6282666a45a3" and "20311740a28645076c07e0ae8e8d55448e41de3f" have entirely different histories.
2b1cabbb43
...
20311740a2
19 changed files with 92 additions and 487 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -132,7 +132,4 @@ dmypy.json
|
||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
tmp/
|
tmp/
|
||||||
.claude/
|
.claude/settings.json
|
||||||
.vscode/
|
|
||||||
.github
|
|
||||||
CLAUDE.md
|
|
||||||
|
|
|
||||||
91
CLAUDE.md
Normal file
91
CLAUDE.md
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
# 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 <addon> # tests (run, NOT exec: avoids cache)
|
||||||
|
docker-compose exec odoo odoo -d odoo -u <addon> --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/`.
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
======================
|
|
||||||
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 <https://github.com/OCA/pos/commit/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 <https://criptomart.net>`_:
|
|
||||||
|
|
||||||
* Criptomart
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
# Copyright 2026 - Today Criptomart
|
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
||||||
from . import models
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
# 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,
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
from . import pos_payment_method
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
# 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
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
* `Criptomart <https://criptomart.net>`_:
|
|
||||||
|
|
||||||
* Criptomart
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
The 18.0 migration of ``pos_payment_method_cashdro``
|
|
||||||
(`OCA/pos@6a4c251d <https://github.com/OCA/pos/commit/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.
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
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.
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
/* 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;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
/* 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;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
from . import test_load_pos_data_fields
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
# 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)
|
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
From 11c65e8000282b9a761346f1fbff16e09581fc17 Mon Sep 17 00:00:00 2001
|
|
||||||
From: Criptomart <ofi@criptomart.net>
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
# Copyright Criptomart
|
|
||||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"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,
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
/* 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 });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue