[IMP] point_of_sale: generic electronic payment interface

This is an interface that can be implemented to support some
electronic payment method in the POS. It's extracted from the existing
pos_iot code.

To implement a payment method you:

- Python: add a selection to the use_payment_terminal field in Python
- JS: implement the PaymentInterface class
- JS: register your PaymentInterface in point_of_sale.models:

models.register_payment_method(
  'use_payment_terminal_selection_value', ImplementedPaymentInterface
);

See the documentation of PaymentInterface for details on what each
method does and when it is called.

Co-authored-by: Antoine Prieels <anp@odoo.com>
This commit is contained in:
Joren Van Onder
2019-08-23 13:33:28 -07:00
co-authored by Antoine Prieels
parent 296c2ea13d
commit cd4b10b004
12 changed files with 491 additions and 35 deletions
+2
View File
@@ -59,6 +59,8 @@ class PosOrder(models.Model):
'amount': ui_paymentline['amount'] or 0.0,
'payment_date': payment_date,
'payment_method_id': ui_paymentline['payment_method_id'],
'card_type': ui_paymentline.get('card_type'),
'transaction_id': ui_paymentline.get('transaction_id'),
'pos_order_id': order.id,
}
@@ -23,3 +23,5 @@ class PosPayment(models.Model):
partner_id = fields.Many2one('res.partner', string='Customer', related='pos_order_id.partner_id')
session_id = fields.Many2one('pos.session', string='Session', related='pos_order_id.session_id')
company_id = fields.Many2one('res.company', string='Company', related='pos_order_id.company_id')
card_type = fields.Char('Type of card used')
transaction_id = fields.Char('Payment Transaction ID')
@@ -40,6 +40,14 @@ class PosPaymentMethod(models.Model):
open_session_ids = fields.Many2many('pos.session', string='Pos Sessions', compute='_compute_open_session_ids', help='Open PoS sessions that are using this payment method.')
config_ids = fields.Many2many('pos.config', string='Point of Sale Configurations')
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
use_payment_terminal = fields.Selection([], 'Use a Payment Terminal', help='Record payments with a terminal on this journal.')
hide_use_payment_terminal = fields.Boolean(compute='_compute_hide_use_payment_terminal', help='Technical field which is used to '
'hide use_payment_terminal when no payment interfaces are installed.')
def _compute_hide_use_payment_terminal(self):
no_terminals = not bool(self._fields['use_payment_terminal'].selection)
for payment_method in self:
payment_method.hide_use_payment_terminal = no_terminals
@api.depends('config_ids')
def _compute_open_session_ids(self):
@@ -1428,6 +1428,16 @@ td {
text-align: center;
}
.paymentlines .button {
cursor: pointer;
border: 1px solid #cacaca;
}
.paymentlines .electronic_payment {
background: #e2e2e2;
border-collapse: unset;
text-align: center;
font-size: 16px;
}
/* c) The receipt screen */
+113 -6
View File
@@ -380,7 +380,7 @@ exports.PosModel = Backbone.Model.extend({
},
},{
model: 'pos.payment.method',
fields: ['name', 'is_cash_count'],
fields: ['name', 'is_cash_count', 'use_payment_terminal'],
domain: function(self, tmp) {
return [['id', 'in', tmp.payment_method_ids]];
},
@@ -395,10 +395,15 @@ exports.PosModel = Backbone.Model.extend({
return a.id - b.id;
}
});
self.payment_methods_by_id = {}
self.payment_methods_by_id = {};
_.each(self.payment_methods, function(payment_method) {
self.payment_methods_by_id[payment_method.id] = payment_method
})
self.payment_methods_by_id[payment_method.id] = payment_method;
var PaymentInterface = self.electronic_payment_interfaces[payment_method.use_payment_terminal];
if (PaymentInterface) {
payment_method.payment_terminal = new PaymentInterface(self, payment_method);
}
});
}
},{
model: 'account.fiscal.position',
@@ -1166,8 +1171,25 @@ exports.PosModel = Backbone.Model.extend({
}
},
electronic_payment_interfaces: {},
});
/**
* Call this function to map your PaymentInterface implementation to
* the use_payment_terminal field. When the POS loads it will take
* care of instantiating your interface and setting it on the right
* payment methods.
*
* @param {string} use_payment_terminal - value used in the
* use_payment_terminal selection field
*
* @param {Object} ImplementedPaymentInterface - implemented
* PaymentInterface
*/
exports.register_payment_method = function(use_payment_terminal, ImplementedPaymentInterface) {
exports.PosModel.prototype.electronic_payment_interfaces[use_payment_terminal] = ImplementedPaymentInterface;
};
// Add fields to the list of read fields when a model is loaded
// by the point of sale.
// e.g: module.load_fields("product.product",['price','category'])
@@ -2064,6 +2086,11 @@ exports.Paymentline = Backbone.Model.extend({
this.order = options.order;
this.amount = 0;
this.selected = false;
this.ticket = '';
this.payment_status = '';
this.card_type = '';
this.transaction_id = '';
if (options.json) {
this.init_from_JSON(options.json);
return;
@@ -2077,6 +2104,10 @@ exports.Paymentline = Backbone.Model.extend({
init_from_JSON: function(json){
this.amount = json.amount;
this.payment_method = this.pos.payment_methods_by_id[json.payment_method_id];
this.payment_status = json.payment_status;
this.ticket = json.ticket;
this.card_type = json.card_type;
this.transaction_id = json.transaction_id;
},
//sets the amount of money on this payment line
set_amount: function(value){
@@ -2097,13 +2128,45 @@ exports.Paymentline = Backbone.Model.extend({
this.trigger('change',this);
}
},
/**
* returns {string} payment status.
*/
get_payment_status: function() {
return this.payment_status;
},
/**
* Set the new payment status.
*
* @param {string} value - new status.
*/
set_payment_status: function(value) {
this.payment_status = value;
this.trigger('change', this);
},
/**
* Set additional info to be printed on the receipts. value should
* be compatible with both the QWeb and ESC/POS receipts.
*
* @param {string} value - receipt info
*/
set_receipt_info: function(value) {
this.ticket += value;
this.trigger('change', this);
},
// returns the associated cashregister
//exports as JSON for server communication
export_as_JSON: function(){
return {
name: time.datetime_to_str(new Date()),
payment_method_id: this.payment_method.id,
amount: this.get_amount()
amount: this.get_amount(),
payment_status: this.payment_status,
ticket: this.ticket,
card_type: this.card_type,
transaction_id: this.transaction_id,
};
},
//exports as JSON for receipt printing
@@ -2616,6 +2679,17 @@ exports.Order = Backbone.Model.extend({
get_paymentlines: function(){
return this.paymentlines.models;
},
/**
* Retrieve the paymentline with the specified cid
*
* @param {String} cid
*/
get_paymentline: function (cid) {
var lines = this.get_paymentlines();
return lines.find(function (line) {
return line.cid === cid;
});
},
remove_paymentline: function(line){
this.assert_editable();
if(this.selected_paymentline === line){
@@ -2647,6 +2721,32 @@ exports.Order = Backbone.Model.extend({
this.trigger('change:selected_paymentline',this.selected_paymentline);
}
},
electronic_payment_in_progress: function() {
return this.get_paymentlines()
.some(function(pl) {
if (pl.payment_status) {
return !['done', 'reversed'].includes(pl.payment_status);
} else {
return false;
}
});
},
/**
* Stops a payment on the terminal if one is running
*/
stop_electronic_payment: function () {
var lines = this.get_paymentlines();
var line = lines.find(function (line) {
var status = line.get_payment_status();
return status && !['done', 'reversed', 'reversing', 'pending', 'retry'].includes(status);
});
if (line) {
line.set_payment_status('waitingCancel');
line.payment_method.payment_terminal.send_payment_cancel(this, line.cid).finally(function () {
line.set_payment_status('retry');
});
}
},
/* ---- Payment Status --- */
get_subtotal : function(){
return round_pr(this.orderlines.reduce((function(sum, orderLine){
@@ -2702,7 +2802,14 @@ exports.Order = Backbone.Model.extend({
},
get_total_paid: function() {
return round_pr(this.paymentlines.reduce((function(sum, paymentLine) {
return sum + paymentLine.get_amount();
if (paymentLine.get_payment_status()) {
if (paymentLine.get_payment_status() == 'done') {
sum += paymentLine.get_amount();
}
} else {
sum += paymentLine.get_amount();
}
return sum;
}), 0), this.pos.currency.rounding);
},
get_tax_details: function(){
@@ -0,0 +1,95 @@
odoo.define('point_of_sale.PaymentInterface', function (require) {
"use strict";
var core = require('web.core');
/**
* Implement this interface to support a new payment method in the POS:
*
* var PaymentInterface = require('point_of_sale.PaymentInterface');
* var MyPayment = PaymentInterface.extend({
* ...
* })
*
* To connect the interface to the right payment methods register it:
*
* var models = require('point_of_sale.models');
* models.register_payment_method('my_payment', MyPayment);
*
* my_payment is the technical name of the added selection in
* use_payment_terminal.
*
* If necessary new fields can be loaded on any model:
*
* models.load_fields('pos.payment.method', ['new_field1', 'new_field2']);
*/
var PaymentInterface = core.Class.extend({
init: function (pos, payment_method) {
this.pos = pos;
this.payment_method = payment_method;
this.supports_reversals = false;
},
/**
* Call this function to enable UI elements that allow a user to
* reverse a payment. This requires that you implement
* send_payment_reversal.
*/
enable_reversals: function () {
this.supports_reversals = true;
},
/**
* Called when a user clicks the "Send" button in the
* interface. This should initiate a payment request and return a
* Promise that resolves when the final status of the payment line
* is set with set_payment_status.
*
* For successful transactions set_receipt_info() should be used
* to set info that should to be printed on the receipt. You
* should also set card_type and transaction_id on the line for
* successful transactions.
*
* @param {string} cid - The id of the paymentline
* @returns {Promise} resolved with a boolean that is false when
* the payment should be retried. Rejected when the status of the
* paymentline will be manually updated.
*/
send_payment_request: function (cid) {},
/**
* Called when a user removes a payment line that's still waiting
* on send_payment_request to complete. Should execute some
* request to ensure the current payment request is
* cancelled. This is not to refund payments, only to cancel
* them. The payment line being cancelled will be deleted
* automatically after the returned promise resolves.
*
* @param {} order - The order of the paymentline
* @param {string} cid - The id of the paymentline
* @returns {Promise}
*/
send_payment_cancel: function (order, cid) {},
/**
* This is an optional method. When implementing this make sure to
* call enable_reversals() in the constructor of your
* interface. This should reverse a previous payment with status
* 'done'. The paymentline will be removed based on returned
* Promise.
*
* @param {string} cid - The id of the paymentline
* @returns {Promise} returns true if the reversal was successful.
*/
send_payment_reversal: function (cid) {},
/**
* Called when the payment screen in the POS is closed (by
* e.g. clicking the "Back" button). Could be used to cancel in
* progress payments.
*/
close: function () {},
});
return PaymentInterface;
});
+129 -10
View File
@@ -131,6 +131,10 @@ var ScreenWidget = PosBaseWidget.extend({
if(this.pos.barcode_reader){
this.pos.barcode_reader.reset_action_callbacks();
}
if(this.payment_interface){
this.payment_interface.close();
}
},
// this methods hides the screen. It's not a good place to put your cleanup stuff as it is called on the
@@ -1869,6 +1873,13 @@ var PaymentScreenWidget = ScreenWidget.extend({
// handle both keyboard and numpad input. Accepts
// a string that represents the key pressed.
payment_input: function(input) {
var paymentline = this.pos.get_order().selected_paymentline;
// disable changing amount on paymentlines with running or done payments on a payment terminal
if (this.payment_interface && !['pending', 'retry'].includes(paymentline.get_payment_status())) {
return;
}
var newbuf = this.gui.numpad_input(this.inputbuffer, input, {'firstinput': this.firstinput});
this.firstinput = (newbuf.length === 0);
@@ -1881,14 +1892,14 @@ var PaymentScreenWidget = ScreenWidget.extend({
if (newbuf !== this.inputbuffer) {
this.inputbuffer = newbuf;
var order = this.pos.get_order();
if (order.selected_paymentline) {
if (paymentline) {
var amount = this.inputbuffer;
if (this.inputbuffer !== "-") {
amount = field_utils.parse.float(this.inputbuffer);
}
order.selected_paymentline.set_amount(amount);
paymentline.set_amount(amount);
this.order_changes();
this.render_paymentlines();
this.$('.paymentline.selected .edit').text(this.format_currency_no_symbol(amount));
@@ -1923,8 +1934,16 @@ var PaymentScreenWidget = ScreenWidget.extend({
click_delete_paymentline: function(cid){
var lines = this.pos.get_order().get_paymentlines();
for ( var i = 0; i < lines.length; i++ ) {
if (lines[i].cid === cid) {
this.pos.get_order().remove_paymentline(lines[i]);
var line = lines[i];
if (line.cid === cid) {
// If a paymentline with a payment terminal linked to
// it is removed, the terminal should get a cancel
// request.
if (['waiting', 'waitingCard', 'timeout'].includes(lines[i].get_payment_status())) {
line.payment_method.payment_terminal.send_payment_cancel(this.pos.get_order(), cid);
}
this.pos.get_order().remove_paymentline(line);
this.reset_input();
this.render_paymentlines();
return;
@@ -1942,6 +1961,82 @@ var PaymentScreenWidget = ScreenWidget.extend({
}
}
},
/**
* link the proper functions to buttons for payment terminals
* send_payment_request, force_payment_done and cancel_payment.
*/
render_payment_terminal: function() {
var self = this;
var order = this.pos.get_order();
if (!order) {
return;
}
this.$el.find('.send_payment_request').click(function () {
var cid = $(this).data('cid');
// Other payment lines can not be reversed anymore
order.get_paymentlines().forEach(function (line) {
line.can_be_reversed = false;
});
var line = self.pos.get_order().get_paymentline(cid);
var payment_terminal = line.payment_method.payment_terminal;
line.set_payment_status('waiting');
self.render_paymentlines();
payment_terminal.send_payment_request(cid).then(function (payment_successful) {
if (payment_successful) {
line.set_payment_status('done');
line.can_be_reversed = self.payment_interface.supports_reversals;
self.reset_input(); // in case somebody entered a tip the amount tendered should be updated
} else {
line.set_payment_status('retry');
}
}).finally(function () {
self.render_paymentlines();
});
self.render_paymentlines();
});
this.$el.find('.send_payment_cancel').click(function () {
var cid = $(this).data('cid');
var line = self.pos.get_order().get_paymentline($(this).data('cid'));
var payment_terminal = line.payment_method.payment_terminal;
line.set_payment_status('waitingCancel');
self.render_paymentlines();
payment_terminal.send_payment_cancel(self.pos.get_order(), cid).finally(function () {
line.set_payment_status('retry');
self.render_paymentlines();
});
self.render_paymentlines();
});
this.$el.find('.send_payment_reversal').click(function () {
var cid = $(this).data('cid');
var line = self.pos.get_order().get_paymentline($(this).data('cid'));
var payment_terminal = line.payment_method.payment_terminal;
line.set_payment_status('reversing');
self.render_paymentlines();
payment_terminal.send_payment_reversal(cid).then(function (reversal_successful) {
if (reversal_successful) {
line.set_amount(0);
line.set_payment_status('reversed');
} else {
line.set_payment_status('done');
}
self.render_paymentlines();
});
});
this.$el.find('.send_force_done').click(function () {
var line = self.pos.get_order().get_paymentline($(this).data('cid'));
var payment_terminal = line.payment_method.payment_terminal;
line.set_payment_status('done');
self.render_paymentlines();
});
},
render_paymentlines: function() {
var self = this;
var order = this.pos.get_order();
@@ -1969,20 +2064,38 @@ var PaymentScreenWidget = ScreenWidget.extend({
});
lines.appendTo(this.$('.paymentlines-container'));
this.render_payment_terminal();
},
compute_extradue: function (order) {
var lines = order.get_paymentlines();
var due = order.get_due();
if (due && lines.length && due !== order.get_due(lines[lines.length-1])) {
if (due && lines.length && (due !== order.get_due(lines[lines.length-1]) || lines[lines.length - 1].payment_status === 'reversed')) {
return due;
}
return 0;
},
click_paymentmethods: function(id) {
var payment_method = this.pos.payment_methods_by_id[id]
this.pos.get_order().add_paymentline(payment_method);
this.reset_input();
this.render_paymentlines();
var payment_method = this.pos.payment_methods_by_id[id];
var order = this.pos.get_order();
if (order.electronic_payment_in_progress()) {
this.gui.show_popup('error',{
'title': _t('Error'),
'body': _t('There is already an electronic payment in progress.'),
});
} else {
order.add_paymentline(payment_method);
this.reset_input();
this.payment_interface = payment_method.payment_terminal;
if (this.payment_interface) {
order.selected_paymentline.set_payment_status('pending');
}
this.render_paymentlines();
this.order_changes();
}
},
render_paymentmethods: function() {
var self = this;
@@ -2082,6 +2195,10 @@ var PaymentScreenWidget = ScreenWidget.extend({
hide: function(){
$('body').off('keypress', this.keyboard_handler);
$('body').off('keydown', this.keyboard_keydown_handler);
var order = this.pos.get_order();
if (order) {
order.stop_electronic_payment();
}
this._super();
},
// sets up listeners to watch for order changes
@@ -2092,7 +2209,9 @@ var PaymentScreenWidget = ScreenWidget.extend({
return;
}
if(this.old_order){
this.old_order.unbind(null,null,this);
this.old_order.stop_electronic_payment();
this.old_order.unbind(null, null, this);
this.old_order.paymentlines.unbind(null, null, this);
}
order.bind('all',function(){
self.order_changes();
+116 -7
View File
@@ -564,6 +564,82 @@
</div>
</t>
<t t-name="PaymentScreenElectronicPayment">
<tr class="send_request paymentline electronic_payment"/>
<tr class="paymentline electronic_payment">
<t t-if="line.payment_status == 'pending'">
<td colspan="3">
Payment request pending
</td>
<td colspan="2" class='button send_payment_request highlight' title="Send Payment Request" t-att-data-cid='line.cid'>
Send
</td>
</t>
<t t-elif="line.payment_status == 'retry'">
<td colspan="3">
Transaction cancelled
</td>
<td colspan="2" class='button send_payment_request highlight' title="Send Payment Request" t-att-data-cid='line.cid'>
Retry
</td>
</t>
<t t-elif="line.payment_status == 'force_done'">
<td colspan="3">
Connection error
</td>
<td colspan="2" class='button send_force_done' title="Force Done" t-att-data-cid='line.cid'>
Force done
</td>
</t>
<t t-elif="line.payment_status == 'waitingCard'">
<td colspan="3">
Waiting for card
</td>
<td colspan="2" class='button send_payment_cancel' title="Cancel Payment Request" t-att-data-cid='line.cid'>
Cancel
</td>
</t>
<t t-elif="['waiting', 'waitingCancel'].includes(line.payment_status)">
<td colspan="3">
Request sent
</td>
<td colspan="2">
<i class='fa fa-spinner fa-spin' role='img'/>
</td>
</t>
<t t-elif="line.payment_status == 'reversing'">
<td colspan="3">
Reversal request sent to terminal
</td>
<td colspan="2">
<i class='fa fa-spinner fa-spin' role='img'/>
</td>
</t>
<t t-elif="line.payment_status == 'done'">
<t t-if="line.can_be_reversed">
<td colspan="3">
Payment Successful
</td>
<td colspan="2" class='button send_payment_reversal' title="Reverse Payment" t-att-data-cid='line.cid'>
Reverse
</td>
</t>
<t t-else="">
<td colspan="5">
Payment Successful
</td>
</t>
</t>
<t t-elif="line.payment_status == 'reversed'">
<tr class="paymentline electronic_payment">
<td colspan="5">
Payment reversed
</td>
</tr>
</t>
</tr>
</t>
<t t-name="PaymentScreen-Paymentlines">
<t t-if="!paymentlines.length">
<div class='paymentlines-empty'>
@@ -599,10 +675,16 @@
<t t-if='line.selected'>
<tr class='paymentline selected'>
<td class='col-due'> <t t-esc='widget.format_currency_no_symbol(order.get_due(line))' /> </td>
<td class='col-tendered edit'>
<t t-esc='widget.inputbuffer' />
<!-- <t t-esc='line.get_amount()' /> -->
</td>
<t t-if="line &amp;&amp; line.payment_status &amp;&amp; ['done', 'waitingCard', 'waiting', 'reversing', 'reversed'].includes(line.payment_status)">
<td class='col-tendered'>
<t t-esc='widget.format_currency_no_symbol(line.get_amount())' />
</td>
</t>
<t t-else="">
<td class='col-tendered edit'>
<t t-esc='widget.inputbuffer' />
</td>
</t>
<t t-if='order.get_change(line)'>
<td class='col-change highlight' >
<t t-esc='widget.format_currency_no_symbol(order.get_change(line))' />
@@ -613,7 +695,18 @@
</t>
<td class='col-name' > <t t-esc='line.name' /> </td>
<td class='delete-button' t-att-data-cid='line.cid' aria-label="Delete" title="Delete"> <i class='fa fa-times-circle' /> </td>
<t t-if="line &amp;&amp; line.payment_status &amp;&amp; ['done', 'reversed', 'reversing'].includes(line.payment_status)">
<td/>
</t>
<t t-else=''>
<td class='delete-button' t-att-data-cid='line.cid' aria-label="Delete" title="Delete">
<i class='fa fa-times-circle'/>
</td>
</t>
<t t-if="line &amp;&amp; line.payment_status">
<t t-call="PaymentScreenElectronicPayment"/>
</t>
</tr>
</t>
<t t-if='!line.selected'>
@@ -626,7 +719,14 @@
</t>
</td>
<td class='col-name'> <t t-esc='line.name' /> </td>
<td class='delete-button' t-att-data-cid='line.cid' aria-label="Delete" title="Delete"> <i class='fa fa-times-circle' /> </td>
<t t-if="line &amp;&amp; line.payment_status &amp;&amp; ['done', 'reversed', 'reversing'].includes(line.payment_status)">
<td/>
</t>
<t t-else=''>
<td class='delete-button' t-att-data-cid='line.cid' aria-label="Delete" title="Delete">
<i class='fa fa-times-circle'/>
</td>
</t>
</tr>
</t>
</t>
@@ -938,7 +1038,16 @@
<br/>
</div>
<div class='after-footer' />
<div class='after-footer'>
<t t-foreach='paymentlines' t-as='line'>
<t t-if='line.ticket'>
<br />
<div class="pos-payment-terminal-receipt">
<t t-raw='line.ticket'/>
</div>
</t>
</t>
</div>
<br/>
<div class="pos-receipt-order-data">
@@ -33,16 +33,16 @@
<button name="set_default_cashbox" type="object" string="Set Default Cash Opening" />
</xpath>
</field>
</record>
</record>
<record id="view_account_journal_pos_user_form" model="ir.ui.view">
<field name="name">account.journal.pos.user.form.inherit</field>
<field name="model">account.journal</field>
<field name="inherit_id" ref="account.view_account_journal_form"/>
<field name="arch" type="xml">
<xpath expr="//notebook[last()]" position="inside">
<page string="Point of Sale" name="point_of_sale" invisible="1"/>
</xpath>
</field>
</record>
<record id="view_account_journal_pos_user_form" model="ir.ui.view">
<field name="name">account.journal.pos.user.form.inherit</field>
<field name="model">account.journal</field>
<field name="inherit_id" ref="account.view_account_journal_form"/>
<field name="arch" type="xml">
<xpath expr="//notebook[last()]" position="inside">
<page string="Point of Sale" name="point_of_sale" invisible="1"/>
</xpath>
</field>
</record>
</odoo>
+1 -1
View File
@@ -6,7 +6,6 @@
<link rel="stylesheet" href="/point_of_sale/static/src/css/pos.css" id="pos-stylesheet"/>
<link rel="stylesheet" href="/point_of_sale/static/src/css/keyboard.css"/>
<link rel="stylesheet" href="/point_of_sale/static/src/css/pos_receipts.css"/>
<link rel="stylesheet" type="text/less" href="/web/static/src/scss/fontawesome_overridden.scss"/>
<script type="text/javascript" src="/point_of_sale/static/lib/backbone/backbone.js"></script>
@@ -23,6 +22,7 @@
<script type="text/javascript" src="/point_of_sale/static/src/js/gui.js"></script>
<script type="text/javascript" src="/point_of_sale/static/src/js/popups.js"></script>
<script type="text/javascript" src="/point_of_sale/static/src/js/screens.js"></script>
<script type="text/javascript" src="/point_of_sale/static/src/js/payment.js"></script>
</template>
<template id="customer_facing_display_html" >
@@ -12,10 +12,12 @@
</div>
<group name="Payment methods">
<group>
<field name="hide_use_payment_terminal" invisible="1"/>
<field name="receivable_account_id" groups="account.group_account_user" />
<field name="is_cash_count" />
<field name="cash_journal_id" attrs="{'invisible': [('is_cash_count', '=', False)], 'required': [('is_cash_count', '=', True)]}" />
<field name="split_transactions" groups="base.group_no_one" />
<field name="use_payment_terminal" attrs="{'invisible': ['|', ('is_cash_count', '=', True), ('hide_use_payment_terminal', '=', True)]}"/>
<field name="company_id" readonly="1" groups="base.group_multi_company" />
</group>
<group>
@@ -12,6 +12,8 @@
<field name="amount" />
<field name="pos_order_id" />
<field name="payment_method_id" />
<field name="card_type" attrs="{'invisible': [('card_type', '=', False)]}"/>
<field name="transaction_id" attrs="{'invisible': [('transaction_id', '=', False)]}"/>
<field name="session_id" />
</group>
</sheet>