add pos_cashdro_refund,pos_cashdro_allow_manual
This commit is contained in:
parent
9b25650118
commit
b7e8f8967c
14 changed files with 409 additions and 0 deletions
2
pos_cashdro_refund/__init__.py
Normal file
2
pos_cashdro_refund/__init__.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Copyright 2026 Criptomart
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
21
pos_cashdro_refund/__manifest__.py
Normal file
21
pos_cashdro_refund/__manifest__.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Copyright 2026 Criptomart
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
{
|
||||
"name": "POS CashDro Refund",
|
||||
"summary": "Permite devoluciones (payouts) a través de la máquina CashDro desde el TPV",
|
||||
"version": "16.0.1.0.0",
|
||||
"category": "Point of Sale",
|
||||
"license": "AGPL-3",
|
||||
"author": "Criptomart",
|
||||
"depends": [
|
||||
"pos_payment_method_cashdro",
|
||||
],
|
||||
"assets": {
|
||||
"point_of_sale.assets": [
|
||||
"pos_cashdro_refund/static/src/js/PasswordInputPopup.js",
|
||||
"pos_cashdro_refund/static/src/js/payment_cashdro_refund.esm.js",
|
||||
"pos_cashdro_refund/static/src/xml/PasswordInputPopup.xml",
|
||||
],
|
||||
},
|
||||
"installable": True,
|
||||
}
|
||||
38
pos_cashdro_refund/static/src/js/PasswordInputPopup.js
Normal file
38
pos_cashdro_refund/static/src/js/PasswordInputPopup.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
odoo.define("pos_cashdro_refund.PasswordInputPopup", function (require) {
|
||||
"use strict";
|
||||
/* Copyright 2026 Criptomart - extracted from pos_payment_method_cashdro
|
||||
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */
|
||||
|
||||
const AbstractAwaitablePopup = require("point_of_sale.AbstractAwaitablePopup");
|
||||
const Registries = require("point_of_sale.Registries");
|
||||
const {_lt} = require("@web/core/l10n/translation");
|
||||
const {onMounted, useRef, useState} = owl;
|
||||
|
||||
class PasswordInputPopup extends AbstractAwaitablePopup {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.state = useState({inputValue: this.props.startingValue});
|
||||
this.inputRef = useRef("input");
|
||||
onMounted(this.onMounted);
|
||||
}
|
||||
onMounted() {
|
||||
this.inputRef.el.focus();
|
||||
}
|
||||
getPayload() {
|
||||
return this.state.inputValue;
|
||||
}
|
||||
}
|
||||
PasswordInputPopup.template = "PasswordInputPopup";
|
||||
PasswordInputPopup.defaultProps = {
|
||||
confirmText: _lt("Ok"),
|
||||
cancelText: _lt("Cancel"),
|
||||
title: "",
|
||||
body: "",
|
||||
startingValue: "",
|
||||
placeholder: "",
|
||||
};
|
||||
|
||||
Registries.Component.add(PasswordInputPopup);
|
||||
|
||||
return PasswordInputPopup;
|
||||
});
|
||||
117
pos_cashdro_refund/static/src/js/payment_cashdro_refund.esm.js
Normal file
117
pos_cashdro_refund/static/src/js/payment_cashdro_refund.esm.js
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
/** @odoo-module */
|
||||
/* Copyright 2026 Criptomart - extracted from pos_payment_method_cashdro
|
||||
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
Adds refund (payout) support to the CashDro payment terminal integration.
|
||||
When the order due amount is negative (i.e. a return/refund), this module:
|
||||
1. Asks the cashier for the CashDro password to authorise the payout.
|
||||
2. Sends a type=3 (payout) request to the CashDro instead of the normal
|
||||
type=4 (collection) request.
|
||||
3. Sets the payment line amount to the negative amount dispensed by the
|
||||
machine (totalout).
|
||||
Normal (positive) payments are delegated unchanged to the original
|
||||
cashdro_send_payment_request method.
|
||||
*/
|
||||
|
||||
import {PaymentCashdro} from "@pos_payment_method_cashdro/js/payment_cashdro.esm";
|
||||
import {Gui} from "point_of_sale.Gui";
|
||||
import {_t} from "web.core";
|
||||
|
||||
/**
|
||||
* Dedicated refund (payout) handler added to the CashDro prototype.
|
||||
*
|
||||
* Keeping this as a SEPARATE named method (rather than inlining it inside an
|
||||
* override of cashdro_send_payment_request) means that pos_cashdro_rounding
|
||||
* can always delegate refunds here regardless of which module loaded last.
|
||||
* Both modules patch cashdro_send_payment_request directly on the prototype,
|
||||
* so the last writer wins for that method; but _cashdro_send_refund_request is
|
||||
* only ever written by this module and is never overwritten.
|
||||
*/
|
||||
PaymentCashdro.prototype._cashdro_send_refund_request = async function (order) {
|
||||
const payment_line = order.selected_paymentline;
|
||||
const due = order.get_due(payment_line);
|
||||
// Include cash rounding if applied (same logic as pos_cashdro_rounding for
|
||||
// normal payments). get_rounding_applied() returns 0 when no rounding is
|
||||
// configured, so this is safe without pos_cashdro_rounding installed.
|
||||
const rounding = order.get_rounding_applied ? order.get_rounding_applied() : 0;
|
||||
const amount = Math.round(Math.abs(due + rounding) * 100);
|
||||
|
||||
// Require password authorisation before opening the drawer for a payout.
|
||||
const {confirmed, payload: password} = await Gui.showPopup(
|
||||
"PasswordInputPopup",
|
||||
{
|
||||
title: _t("CashDro Refund Authorization"),
|
||||
body: _t("Enter the CashDro password to authorize this refund"),
|
||||
placeholder: _t("Password"),
|
||||
}
|
||||
);
|
||||
if (!confirmed) {
|
||||
payment_line.set_payment_status("retry");
|
||||
return false;
|
||||
}
|
||||
const method = payment_line.payment_method;
|
||||
if (password !== method.cashdro_password) {
|
||||
await Gui.showPopup("ErrorPopup", {
|
||||
title: _t("Authentication Failed"),
|
||||
body: _t("The CashDro password is incorrect."),
|
||||
});
|
||||
payment_line.set_payment_status("retry");
|
||||
return false;
|
||||
}
|
||||
|
||||
const start_url = this._cashdro_payout_url({amount: amount});
|
||||
console.log(start_url);
|
||||
const res = await this._cashdro_request(start_url);
|
||||
console.log(res);
|
||||
const operation_id = res.data || "";
|
||||
this.pos.get_order().cashdro_operation = operation_id;
|
||||
|
||||
const ack_url = this._cashdro_ack_url(operation_id);
|
||||
const res_ack = await this._cashdro_request(ack_url);
|
||||
console.log(res_ack);
|
||||
|
||||
const ask_url = this._cashdro_ask_url(operation_id);
|
||||
const operation_data = await this._cashdro_request_payment(ask_url);
|
||||
console.log(operation_data);
|
||||
|
||||
const data = JSON.parse(operation_data.data);
|
||||
payment_line.cashdro_operation_data = data;
|
||||
// CashDro reports the dispensed amount in totalout for payouts.
|
||||
const dispensed = data.operation.totalout / 100;
|
||||
payment_line.set_amount(-dispensed);
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Build the URL for a type=3 (payout/refund) operation on the CashDro.
|
||||
*/
|
||||
PaymentCashdro.prototype._cashdro_payout_url = function (parameters) {
|
||||
const user = this.pos.get_cashier().id || this.pos.user.id;
|
||||
let url = `${this._cashdro_url()}&operation=startOperation&type=3`;
|
||||
url += `&posid=pos-${this.pos.pos_session.name}`;
|
||||
url += `&posuser=${user}`;
|
||||
url += `¶meters=${encodeURIComponent(JSON.stringify(parameters))}`;
|
||||
return url;
|
||||
};
|
||||
|
||||
/**
|
||||
* Override cashdro_send_payment_request to route refunds to
|
||||
* _cashdro_send_refund_request.
|
||||
*
|
||||
* NOTE: if pos_cashdro_rounding is also installed it will overwrite this
|
||||
* override (because "rounding" > "refund" alphabetically), but that is fine
|
||||
* because pos_cashdro_rounding also delegates negative amounts to
|
||||
* _cashdro_send_refund_request. This override only matters when the rounding
|
||||
* module is NOT installed.
|
||||
*/
|
||||
const _originalSendPaymentRequest =
|
||||
PaymentCashdro.prototype.cashdro_send_payment_request;
|
||||
|
||||
PaymentCashdro.prototype.cashdro_send_payment_request = async function (order) {
|
||||
const payment_line = order.selected_paymentline;
|
||||
const due = order.get_due(payment_line);
|
||||
if (due < 0) {
|
||||
return this._cashdro_send_refund_request(order);
|
||||
}
|
||||
return _originalSendPaymentRequest.call(this, order);
|
||||
};
|
||||
33
pos_cashdro_refund/static/src/xml/PasswordInputPopup.xml
Normal file
33
pos_cashdro_refund/static/src/xml/PasswordInputPopup.xml
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright 2026 Criptomart - extracted from pos_payment_method_cashdro
|
||||
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="PasswordInputPopup" owl="1">
|
||||
<div class="popup popup-textinput">
|
||||
<header class="title">
|
||||
<t t-esc="props.title" />
|
||||
</header>
|
||||
<div class="body">
|
||||
<p>
|
||||
<t t-esc="props.body" />
|
||||
</p>
|
||||
<input
|
||||
type="password"
|
||||
t-model="state.inputValue"
|
||||
t-ref="input"
|
||||
t-att-placeholder="props.placeholder"
|
||||
/>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="button confirm highlight" t-on-click="confirm">
|
||||
<t t-esc="props.confirmText" />
|
||||
</div>
|
||||
<div class="button cancel" t-on-click="cancel">
|
||||
<t t-esc="props.cancelText" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
Loading…
Add table
Add a link
Reference in a new issue