[IMP] point_of_sale,account: revamp of accounting in pos
This commit makes some major changes in the Point of Sale module
when posting journal entries which allowed the following new features:
1. Show that the invoice generated is paid unlike before where invoice
doesn't appear to be paid (both in the pdf and in the backend).
2. User can create a 'Pay Later' payment method (created by default as
'Customer Account') to allow the customers to make credit in their
purchases.
3. An extension possibility where POS can settle customers dues.
The above is accomplished by doing the following:
1. Invoices are now reconciled to created account moves by POS using
the pos payments registered to the pos order. And this is done
right after the order is synced to the backend.
2. A new type of pos payment called 'pay_later' is introduced.
- When an order is invoiced and is paid with 'pay_later' payment,
the customer in the invoice will have `due` in his account which is
immediately reflected when the order is synced.
- When an order is not invoiced and is paid with 'pay_later' payment,
the customer's 'due' reflects when the session is closed.
3. When the session is closed, POS payments from cash payment methods
are moved to the default account of the cash journal specified in the
payment method via creation of `account.bank.statement.line`s. (This is
already the case but it is mentioned this is for the purpose of
transparency.)
4. As for the bank pos payments, `account.payment` records are created
to move the payments to the specified `outstanding_account_id` in the
pos payment method. The `account.payment` records are written the bank
journal specified in the payment method.
5. As for the `pay_later` payments, we keep the behavior when no
journal is specified in the payment method where payments become
receivables to the customer who made the payments.
Other notable changes:
- The default POSS journal is changed to have 'general' type in order
to able to view the journal entries written in it.
- `pos.payment.method` can now be categorized as 'cash', 'bank' or 'pay_later'.
- `account_default_pos_receivable_account_id` can now be configured from
the Point of Sale general settings.
- `pos_invoice_report` is removed in favor of `account.account_invoices`
- disallow creating new payment method from `pos.config` form
- 'Journal Items' button in the `pos.session` form are now always visible.
- This allows seeing the list of invoices (and their payment moves)
to be accessible thru this button.
See test cases from the following spreadsheet:
https://docs.google.com/spreadsheets/d/1mt2jRSDU7OONPBFjwyTcnhRjITQI8rGMLLQA5K3fAjo/edit?usp=sharing
Part-of: odoo/odoo#74870
This commit is contained in:
@@ -1558,31 +1558,32 @@ class AccountMove(models.Model):
|
||||
|
||||
def _get_reconciled_info_JSON_values(self):
|
||||
self.ensure_one()
|
||||
|
||||
reconciled_vals = []
|
||||
for partial, amount, counterpart_line in self._get_reconciled_invoices_partials():
|
||||
if counterpart_line.move_id.ref:
|
||||
reconciliation_ref = '%s (%s)' % (counterpart_line.move_id.name, counterpart_line.move_id.ref)
|
||||
else:
|
||||
reconciliation_ref = counterpart_line.move_id.name
|
||||
|
||||
reconciled_vals.append({
|
||||
'name': counterpart_line.name,
|
||||
'journal_name': counterpart_line.journal_id.name,
|
||||
'amount': amount,
|
||||
'currency': self.currency_id.symbol,
|
||||
'digits': [69, self.currency_id.decimal_places],
|
||||
'position': self.currency_id.position,
|
||||
'date': counterpart_line.date,
|
||||
'payment_id': counterpart_line.id,
|
||||
'partial_id': partial.id,
|
||||
'account_payment_id': counterpart_line.payment_id.id,
|
||||
'payment_method_name': counterpart_line.payment_id.payment_method_line_id.name,
|
||||
'move_id': counterpart_line.move_id.id,
|
||||
'ref': reconciliation_ref,
|
||||
})
|
||||
reconciled_vals.append(self._get_reconciled_vals(partial, amount, counterpart_line))
|
||||
return reconciled_vals
|
||||
|
||||
def _get_reconciled_vals(self, partial, amount, counterpart_line):
|
||||
if counterpart_line.move_id.ref:
|
||||
reconciliation_ref = '%s (%s)' % (counterpart_line.move_id.name, counterpart_line.move_id.ref)
|
||||
else:
|
||||
reconciliation_ref = counterpart_line.move_id.name
|
||||
return {
|
||||
'name': counterpart_line.name,
|
||||
'journal_name': counterpart_line.journal_id.name,
|
||||
'amount': amount,
|
||||
'currency': self.currency_id.symbol,
|
||||
'digits': [69, self.currency_id.decimal_places],
|
||||
'position': self.currency_id.position,
|
||||
'date': counterpart_line.date,
|
||||
'payment_id': counterpart_line.id,
|
||||
'partial_id': partial.id,
|
||||
'account_payment_id': counterpart_line.payment_id.id,
|
||||
'payment_method_name': counterpart_line.payment_id.payment_method_line_id.name,
|
||||
'move_id': counterpart_line.move_id.id,
|
||||
'ref': reconciliation_ref,
|
||||
}
|
||||
|
||||
@api.depends('move_type', 'line_ids.amount_residual')
|
||||
def _compute_payments_widget_reconciled_info(self):
|
||||
for move in self:
|
||||
|
||||
@@ -178,12 +178,7 @@ class AccountPayment(models.Model):
|
||||
writeoff_lines = self.env['account.move.line']
|
||||
|
||||
for line in self.move_id.line_ids:
|
||||
if line.account_id in (
|
||||
self.journal_id.default_account_id,
|
||||
self.payment_method_line_id.payment_account_id,
|
||||
self.journal_id.company_id.account_journal_payment_debit_account_id,
|
||||
self.journal_id.company_id.account_journal_payment_credit_account_id,
|
||||
):
|
||||
if line.account_id in self._get_valid_liquidity_accounts():
|
||||
liquidity_lines += line
|
||||
elif line.account_id.internal_type in ('receivable', 'payable') or line.partner_id == line.company_id.partner_id:
|
||||
counterpart_lines += line
|
||||
@@ -192,6 +187,14 @@ class AccountPayment(models.Model):
|
||||
|
||||
return liquidity_lines, counterpart_lines, writeoff_lines
|
||||
|
||||
def _get_valid_liquidity_accounts(self):
|
||||
return (
|
||||
self.journal_id.default_account_id,
|
||||
self.payment_method_line_id.payment_account_id,
|
||||
self.journal_id.company_id.account_journal_payment_debit_account_id,
|
||||
self.journal_id.company_id.account_journal_payment_credit_account_id,
|
||||
)
|
||||
|
||||
def _prepare_move_line_default_vals(self, write_off_line_vals=None):
|
||||
''' Prepare the dictionary to create the default account.move.lines for the current payment.
|
||||
:param write_off_line_vals: Optional dictionary to create a write-off account.move.line easily containing:
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
'views/pos_category_view.xml',
|
||||
'views/product_view.xml',
|
||||
'views/account_journal_view.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/pos_payment_method_views.xml',
|
||||
'views/pos_payment_views.xml',
|
||||
'views/pos_config_view.xml',
|
||||
@@ -35,12 +36,12 @@
|
||||
'data/point_of_sale_data.xml',
|
||||
'views/pos_order_report_view.xml',
|
||||
'views/account_statement_view.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/digest_views.xml',
|
||||
'views/res_partner_view.xml',
|
||||
'views/report_userlabel.xml',
|
||||
'views/report_saledetails.xml',
|
||||
'views/point_of_sale_dashboard.xml',
|
||||
'views/report_invoice.xml',
|
||||
],
|
||||
'demo': [
|
||||
'data/point_of_sale_demo.xml',
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import account_bank_statement
|
||||
from . import account_payment
|
||||
from . import account_journal
|
||||
from . import account_tax
|
||||
from . import account_move
|
||||
|
||||
@@ -7,4 +7,4 @@ from odoo import fields, models, api
|
||||
class AccountJournal(models.Model):
|
||||
_inherit = 'account.journal'
|
||||
|
||||
pos_payment_method_ids = fields.One2many('pos.payment.method', 'cash_journal_id', string='Point of Sale Payment Methods')
|
||||
pos_payment_method_ids = fields.One2many('pos.payment.method', 'journal_id', string='Point of Sale Payment Methods')
|
||||
|
||||
@@ -8,6 +8,7 @@ class AccountMove(models.Model):
|
||||
_inherit = 'account.move'
|
||||
|
||||
pos_order_ids = fields.One2many('pos.order', 'account_move')
|
||||
pos_payment_ids = fields.One2many('pos.payment', 'account_move_id')
|
||||
|
||||
def _stock_account_get_last_step_stock_moves(self):
|
||||
stock_moves = super(AccountMove, self)._stock_account_get_last_step_stock_moves()
|
||||
@@ -40,6 +41,13 @@ class AccountMove(models.Model):
|
||||
|
||||
return lot_values
|
||||
|
||||
def _get_reconciled_vals(self, partial, amount, counterpart_line):
|
||||
"""Add pos_payment_name field in the reconciled vals to be able to show the payment method in the invoice."""
|
||||
result = super()._get_reconciled_vals(partial, amount, counterpart_line)
|
||||
if counterpart_line.move_id.pos_payment_ids:
|
||||
pos_payment = counterpart_line.move_id.pos_payment_ids
|
||||
result['pos_payment_name'] = pos_payment.payment_method_id.name
|
||||
return result
|
||||
|
||||
class AccountMoveLine(models.Model):
|
||||
_inherit = 'account.move.line'
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models, fields, api
|
||||
|
||||
|
||||
class AccountPayment(models.Model):
|
||||
_inherit = 'account.payment'
|
||||
|
||||
pos_payment_method_id = fields.Many2one('pos.payment.method', "POS Payment Method")
|
||||
force_outstanding_account_id = fields.Many2one("account.account", "Forced Outstanding Account", check_company=True)
|
||||
pos_session_id = fields.Many2one('pos.session', "POS Session")
|
||||
|
||||
def _get_valid_liquidity_accounts(self):
|
||||
result = super()._get_valid_liquidity_accounts()
|
||||
return result + (self.pos_payment_method_id.outstanding_account_id,)
|
||||
|
||||
@api.depends("force_outstanding_account_id")
|
||||
def _compute_outstanding_account_id(self):
|
||||
"""When force_outstanding_account_id is set, we use it as the outstanding_account_id."""
|
||||
super()._compute_outstanding_account_id()
|
||||
for payment in self:
|
||||
if payment.force_outstanding_account_id:
|
||||
payment.outstanding_account_id = payment.force_outstanding_account_id
|
||||
@@ -83,7 +83,7 @@ class PosConfig(models.Model):
|
||||
return self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1).pos_type_id.id
|
||||
|
||||
def _default_sale_journal(self):
|
||||
return self.env['account.journal'].search([('type', '=', 'sale'), ('company_id', '=', self.env.company.id), ('code', '=', 'POSS')], limit=1)
|
||||
return self.env['account.journal'].search([('type', 'in', ('sale', 'general')), ('company_id', '=', self.env.company.id), ('code', '=', 'POSS')], limit=1)
|
||||
|
||||
def _default_invoice_journal(self):
|
||||
return self.env['account.journal'].search([('type', '=', 'sale'), ('company_id', '=', self.env.company.id)], limit=1)
|
||||
@@ -111,9 +111,9 @@ class PosConfig(models.Model):
|
||||
domain="[('code', '=', 'outgoing'), ('warehouse_id.company_id', '=', company_id)]",
|
||||
ondelete='restrict')
|
||||
journal_id = fields.Many2one(
|
||||
'account.journal', string='Sales Journal',
|
||||
domain=[('type', '=', 'sale')],
|
||||
help="Accounting journal used to post sales entries.",
|
||||
'account.journal', string='Point of Sale Journal',
|
||||
domain=[('type', 'in', ('general', 'sale'))],
|
||||
help="Accounting journal used to post POS session journal entries and POS invoice payments.",
|
||||
default=_default_sale_journal,
|
||||
ondelete='restrict')
|
||||
invoice_journal_id = fields.Many2one(
|
||||
@@ -364,7 +364,7 @@ class PosConfig(models.Model):
|
||||
def _check_profit_loss_cash_journal(self):
|
||||
if self.cash_control and self.payment_method_ids:
|
||||
for method in self.payment_method_ids:
|
||||
if method.is_cash_count and (not method.cash_journal_id.loss_account_id or not method.cash_journal_id.profit_account_id):
|
||||
if method.is_cash_count and (not method.journal_id.loss_account_id or not method.journal_id.profit_account_id):
|
||||
raise ValidationError(_("You need a loss and profit account on your cash journal."))
|
||||
|
||||
@api.constrains('company_id', 'invoice_journal_id')
|
||||
@@ -393,24 +393,10 @@ class PosConfig(models.Model):
|
||||
if any(
|
||||
self.payment_method_ids\
|
||||
.filtered(lambda pm: pm.is_cash_count)\
|
||||
.mapped(lambda pm: self.currency_id not in (self.company_id.currency_id | pm.cash_journal_id.currency_id))
|
||||
.mapped(lambda pm: self.currency_id not in (self.company_id.currency_id | pm.journal_id.currency_id))
|
||||
):
|
||||
raise ValidationError(_("All payment methods must be in the same currency as the Sales Journal or the company currency if that is not set."))
|
||||
|
||||
@api.constrains('payment_method_ids')
|
||||
def _check_payment_method_receivable_accounts(self):
|
||||
# This is normally not supposed to happen to have a payment method without a receivable account set,
|
||||
# as this is a required field. However, it happens the receivable account cannot be found during upgrades
|
||||
# and this is a bommer to block the upgrade for that point, given the user can correct this by himself,
|
||||
# without requiring a manual intervention from our upgrade support.
|
||||
# However, this must be ensured this receivable is well set before opening a POS session.
|
||||
invalid_payment_methods = self.payment_method_ids.filtered(lambda method: not method.receivable_account_id)
|
||||
if invalid_payment_methods:
|
||||
method_names = ", ".join(method.name for method in invalid_payment_methods)
|
||||
raise ValidationError(
|
||||
_("You must configure an intermediary account for the payment methods: %s.") % method_names
|
||||
)
|
||||
|
||||
def _check_payment_method_ids(self):
|
||||
self.ensure_one()
|
||||
if not self.payment_method_ids:
|
||||
@@ -642,7 +628,6 @@ class PosConfig(models.Model):
|
||||
self._check_currencies()
|
||||
self._check_profit_loss_cash_journal()
|
||||
self._check_payment_method_ids()
|
||||
self._check_payment_method_receivable_accounts()
|
||||
self.env['pos.session'].create({
|
||||
'user_id': self.env.uid,
|
||||
'config_id': self.id
|
||||
@@ -704,21 +689,24 @@ class PosConfig(models.Model):
|
||||
if pos_config.payment_method_ids or pos_config.has_active_session:
|
||||
continue
|
||||
cash_journal = self.env['account.journal'].search([('company_id', '=', company.id), ('type', '=', 'cash')], limit=1)
|
||||
pos_receivable_account = company.account_default_pos_receivable_account_id
|
||||
bank_journal = self.env['account.journal'].search([('company_id', '=', company.id), ('type', '=', 'bank')], limit=1)
|
||||
payment_methods = self.env['pos.payment.method']
|
||||
if cash_journal:
|
||||
payment_methods |= payment_methods.create({
|
||||
'name': _('Cash'),
|
||||
'receivable_account_id': pos_receivable_account.id,
|
||||
'is_cash_count': True,
|
||||
'cash_journal_id': cash_journal.id,
|
||||
'journal_id': cash_journal.id,
|
||||
'company_id': company.id,
|
||||
})
|
||||
if bank_journal:
|
||||
payment_methods |= payment_methods.create({
|
||||
'name': _('Bank'),
|
||||
'journal_id': bank_journal.id,
|
||||
'company_id': company.id,
|
||||
})
|
||||
payment_methods |= payment_methods.create({
|
||||
'name': _('Bank'),
|
||||
'receivable_account_id': pos_receivable_account.id,
|
||||
'is_cash_count': False,
|
||||
'name': _('Customer Account'),
|
||||
'company_id': company.id,
|
||||
'split_transactions': True,
|
||||
})
|
||||
pos_config.write({'payment_method_ids': [(6, 0, payment_methods.ids)]})
|
||||
|
||||
@@ -729,7 +717,7 @@ class PosConfig(models.Model):
|
||||
pos_journal = self.env['account.journal'].search([('company_id', '=', company.id), ('code', '=', 'POSS')])
|
||||
if not pos_journal:
|
||||
pos_journal = self.env['account.journal'].create({
|
||||
'type': 'sale',
|
||||
'type': 'general',
|
||||
'name': 'Point of Sale',
|
||||
'code': 'POSS',
|
||||
'company_id': company.id,
|
||||
|
||||
@@ -536,7 +536,6 @@ class PosOrder(models.Model):
|
||||
self.ensure_one()
|
||||
timezone = pytz.timezone(self._context.get('tz') or self.env.user.tz or 'UTC')
|
||||
vals = {
|
||||
'payment_reference': self.name,
|
||||
'invoice_origin': self.name,
|
||||
'journal_id': self.session_id.config_id.invoice_journal_id.id,
|
||||
'move_type': 'out_invoice' if self.amount_total >= 0 else 'out_refund',
|
||||
@@ -574,6 +573,7 @@ class PosOrder(models.Model):
|
||||
order.write({'account_move': new_move.id, 'state': 'invoiced'})
|
||||
new_move.sudo().with_company(order.company_id)._post()
|
||||
moves += new_move
|
||||
order._apply_invoice_payments()
|
||||
|
||||
if not moves:
|
||||
return {}
|
||||
@@ -594,6 +594,16 @@ class PosOrder(models.Model):
|
||||
def action_pos_order_cancel(self):
|
||||
return self.write({'state': 'cancel'})
|
||||
|
||||
def _apply_invoice_payments(self):
|
||||
receivable_account = self.env["res.partner"]._find_accounting_partner(self.partner_id).property_account_receivable_id
|
||||
payment_moves = self.payment_ids._create_payment_moves()
|
||||
invoice_receivable = self.account_move.line_ids.filtered(lambda line: line.account_id == receivable_account)
|
||||
# Reconcile the invoice to the created payment moves.
|
||||
# But not when the invoice's total amount is zero because it's already reconciled.
|
||||
if not invoice_receivable.reconciled and receivable_account.reconcile:
|
||||
payment_receivables = payment_moves.mapped('line_ids').filtered(lambda line: line.account_id == receivable_account)
|
||||
(invoice_receivable | payment_receivables).reconcile()
|
||||
|
||||
@api.model
|
||||
def create_from_ui(self, orders, draft=False):
|
||||
""" Create and update Orders from the frontend PoS application.
|
||||
@@ -715,7 +725,7 @@ class PosOrder(models.Model):
|
||||
}
|
||||
|
||||
if self.mapped('account_move'):
|
||||
report = self.env.ref('point_of_sale.pos_invoice_report')._render_qweb_pdf(self.ids[0])
|
||||
report = self.env.ref('account.account_invoices')._render_qweb_pdf(self.account_move.ids[0])
|
||||
filename = name + '.pdf'
|
||||
attachment = self.env['ir.attachment'].create({
|
||||
'name': filename,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.tools import formatLang
|
||||
from odoo.tools import formatLang, float_is_zero
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ class PosPayment(models.Model):
|
||||
payment_status = fields.Char('Payment Status')
|
||||
ticket = fields.Char('Payment Receipt Info')
|
||||
is_change = fields.Boolean(string='Is this payment change?', default=False)
|
||||
account_move_id = fields.Many2one('account.move')
|
||||
|
||||
@api.model
|
||||
def name_get(self):
|
||||
@@ -62,3 +63,35 @@ class PosPayment(models.Model):
|
||||
|
||||
def export_for_ui(self):
|
||||
return self.mapped(self._export_for_ui) if self else []
|
||||
|
||||
def _create_payment_moves(self):
|
||||
result = self.env['account.move']
|
||||
for payment in self:
|
||||
order = payment.pos_order_id
|
||||
payment_method = payment.payment_method_id
|
||||
if payment_method.type == 'pay_later' or float_is_zero(payment.amount, precision_rounding=order.currency_id.rounding):
|
||||
continue
|
||||
accounting_partner = self.env["res.partner"]._find_accounting_partner(payment.partner_id)
|
||||
pos_session = order.session_id
|
||||
journal = pos_session.config_id.journal_id
|
||||
payment_move = self.env['account.move'].with_context(default_journal_id=journal.id).create({
|
||||
'journal_id': journal.id,
|
||||
'date': fields.Date.context_today(payment),
|
||||
'ref': _('Invoice payment for %s (%s) using %s') % (order.name, order.account_move.name, payment_method.name),
|
||||
'pos_payment_ids': payment.ids,
|
||||
})
|
||||
result |= payment_move
|
||||
payment.write({'account_move_id': payment_move.id})
|
||||
amounts = pos_session._update_amounts({'amount': 0, 'amount_converted': 0}, {'amount': payment.amount}, payment.payment_date)
|
||||
credit_line_vals = pos_session._credit_amounts({
|
||||
'account_id': accounting_partner.property_account_receivable_id.id,
|
||||
'partner_id': accounting_partner.id,
|
||||
'move_id': payment_move.id,
|
||||
}, amounts['amount'], amounts['amount_converted'])
|
||||
debit_line_vals = pos_session._debit_amounts({
|
||||
'account_id': pos_session.company_id.account_default_pos_receivable_account_id.id,
|
||||
'move_id': payment_move.id,
|
||||
}, amounts['amount'], amounts['amount_converted'])
|
||||
self.env['account.move.line'].with_context(check_move_validity=False).create([credit_line_vals, debit_line_vals])
|
||||
payment_move._post()
|
||||
return result
|
||||
|
||||
@@ -3,18 +3,6 @@ from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class PosPaymentMethod(models.Model):
|
||||
""" Used to classify pos.payment.
|
||||
|
||||
Generic characteristics of a pos.payment is described in this model.
|
||||
E.g. A cash payment can be described by a pos.payment.method with
|
||||
fields: is_cash_count = True and a cash_journal_id set to an
|
||||
`account.journal` (type='cash') record.
|
||||
|
||||
When a pos.payment.method is cash, cash_journal_id is required as
|
||||
it will be the journal where the account.bank.statement.line records
|
||||
will be created.
|
||||
"""
|
||||
|
||||
_name = "pos.payment.method"
|
||||
_description = "Point of Sale Payment Methods"
|
||||
_order = "id asc"
|
||||
@@ -22,24 +10,31 @@ class PosPaymentMethod(models.Model):
|
||||
def _get_payment_terminal_selection(self):
|
||||
return []
|
||||
|
||||
name = fields.Char(string="Payment Method", required=True, translate=True)
|
||||
name = fields.Char(string="Method", required=True, translate=True, help='Defines the name of the payment method that will be displayed in the Point of Sale when the payments are selected.')
|
||||
outstanding_account_id = fields.Many2one('account.account',
|
||||
string='Outstanding Account',
|
||||
ondelete='restrict',
|
||||
help='Leave empty to use the default account from the company setting.\n'
|
||||
'Account used as outstanding account when creating accounting payment records for bank payments.')
|
||||
receivable_account_id = fields.Many2one('account.account',
|
||||
string='Intermediary Account',
|
||||
required=True,
|
||||
ondelete='restrict',
|
||||
domain=[('reconcile', '=', True), ('user_type_id.type', '=', 'receivable')],
|
||||
default=lambda self: self.env.company.account_default_pos_receivable_account_id,
|
||||
help="Leave empty to use the default account from the company setting.\n"
|
||||
"Overrides the company's receivable account (for Point of Sale) used in the journal entries.")
|
||||
is_cash_count = fields.Boolean(string='Cash', compute="_compute_is_cash_count", store=True)
|
||||
journal_id = fields.Many2one('account.journal',
|
||||
string='Journal',
|
||||
domain=[('type', 'in', ('cash', 'bank'))],
|
||||
ondelete='restrict',
|
||||
help='Account used as counterpart of the income account in the accounting entry representing the pos sales.')
|
||||
is_cash_count = fields.Boolean(string='Cash')
|
||||
cash_journal_id = fields.Many2one('account.journal',
|
||||
string='Cash Journal',
|
||||
domain=[('type', '=', 'cash')],
|
||||
ondelete='restrict',
|
||||
help='The payment method is of type cash. A cash statement will be automatically generated.')
|
||||
help='Leave empty to use the receivable account of customer.\n'
|
||||
'Defines the journal where to book the accumulated payments (or individual payment if Identify Customer is true) after closing the session.\n'
|
||||
'For cash journal, we directly write to the default account in the journal via statement lines.\n'
|
||||
'For bank journal, we write to the outstanding account specified in this payment method.')
|
||||
split_transactions = fields.Boolean(
|
||||
string='Split Transactions',
|
||||
string='Identify Customer',
|
||||
default=False,
|
||||
help='If ticked, each payment will generate a separated journal item. Ticking that option will slow the closing of the PoS.')
|
||||
help='Forces to set a customer when using this payment method and splits the journal entries for each customer. It could slow down the closing process.')
|
||||
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)
|
||||
@@ -47,12 +42,13 @@ class PosPaymentMethod(models.Model):
|
||||
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.')
|
||||
active = fields.Boolean(default=True)
|
||||
type = fields.Selection(selection=[('cash', 'Cash'), ('bank', 'Bank'), ('pay_later', 'Customer Account')], compute="_compute_type")
|
||||
|
||||
@api.depends('is_cash_count')
|
||||
@api.depends('type')
|
||||
def _compute_hide_use_payment_terminal(self):
|
||||
no_terminals = not bool(self._fields['use_payment_terminal'].selection(self))
|
||||
for payment_method in self:
|
||||
payment_method.hide_use_payment_terminal = no_terminals or payment_method.is_cash_count
|
||||
payment_method.hide_use_payment_terminal = no_terminals or payment_method.type in ('cash', 'pay_later')
|
||||
|
||||
@api.onchange('use_payment_terminal')
|
||||
def _onchange_use_payment_terminal(self):
|
||||
@@ -64,13 +60,24 @@ class PosPaymentMethod(models.Model):
|
||||
for payment_method in self:
|
||||
payment_method.open_session_ids = self.env['pos.session'].search([('config_id', 'in', payment_method.config_ids.ids), ('state', '!=', 'closed')])
|
||||
|
||||
@api.onchange('is_cash_count')
|
||||
def _onchange_is_cash_count(self):
|
||||
if not self.is_cash_count:
|
||||
self.cash_journal_id = False
|
||||
else:
|
||||
@api.depends('journal_id', 'split_transactions')
|
||||
def _compute_type(self):
|
||||
for pm in self:
|
||||
if pm.journal_id.type in {'cash', 'bank'}:
|
||||
pm.type = pm.journal_id.type
|
||||
else:
|
||||
pm.type = 'pay_later'
|
||||
|
||||
@api.onchange('journal_id')
|
||||
def _onchange_journal_id(self):
|
||||
if self.is_cash_count:
|
||||
self.use_payment_terminal = False
|
||||
|
||||
@api.depends('type')
|
||||
def _compute_is_cash_count(self):
|
||||
for pm in self:
|
||||
pm.is_cash_count = pm.type == 'cash'
|
||||
|
||||
def _is_write_forbidden(self, fields):
|
||||
return bool(fields and self.open_session_ids)
|
||||
|
||||
|
||||
@@ -97,6 +97,7 @@ class PosSession(models.Model):
|
||||
total_payments_amount = fields.Float(compute='_compute_total_payments_amount', string='Total Payments Amount')
|
||||
is_in_company_currency = fields.Boolean('Is Using Company Currency', compute='_compute_is_in_company_currency')
|
||||
update_stock_at_closing = fields.Boolean('Stock should be updated at closing')
|
||||
bank_payment_ids = fields.One2many('account.payment', 'pos_session_id', 'Bank Payments', help='Account payments representing aggregated and bank split payments.')
|
||||
|
||||
_sql_constraints = [('uniq_name', 'unique(name)', "The name of this POS Session must be unique !")]
|
||||
|
||||
@@ -159,7 +160,7 @@ class PosSession(models.Model):
|
||||
if not cash_payment_methods:
|
||||
continue
|
||||
for statement in session.statement_ids:
|
||||
if statement.journal_id == cash_payment_methods[0].cash_journal_id:
|
||||
if statement.journal_id == cash_payment_methods[0].journal_id:
|
||||
session.cash_control = session.config_id.cash_control
|
||||
session.cash_journal_id = statement.journal_id.id
|
||||
session.cash_register_id = statement.id
|
||||
@@ -210,7 +211,7 @@ class PosSession(models.Model):
|
||||
statement_ids = self.env['account.bank.statement']
|
||||
if self.user_has_groups('point_of_sale.group_pos_user'):
|
||||
statement_ids = statement_ids.sudo()
|
||||
for cash_journal in cash_payment_methods.mapped('cash_journal_id'):
|
||||
for cash_journal in cash_payment_methods.mapped('journal_id'):
|
||||
ctx['journal_id'] = cash_journal.id if pos_config.cash_control and cash_journal.type == 'cash' else False
|
||||
st_values = {
|
||||
'journal_id': cash_journal.id,
|
||||
@@ -344,11 +345,11 @@ class PosSession(models.Model):
|
||||
|
||||
if self.move_id.line_ids:
|
||||
self.move_id._post()
|
||||
self._reconcile_account_move_lines(data)
|
||||
# Set the uninvoiced orders' state to 'done'
|
||||
self.env['pos.order'].search([('session_id', '=', self.id), ('state', '=', 'paid')]).write({'state': 'done'})
|
||||
else:
|
||||
self.move_id.unlink()
|
||||
self._reconcile_account_move_lines(data)
|
||||
else:
|
||||
statement = self.cash_register_id
|
||||
if not self.config_id.cash_control:
|
||||
@@ -356,12 +357,7 @@ class PosSession(models.Model):
|
||||
statement.button_post()
|
||||
statement.button_validate()
|
||||
self.write({'state': 'closed'})
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'name': 'Point of Sale Menu',
|
||||
'tag': 'reload',
|
||||
'params': {'menu_id': self.env.ref('point_of_sale.menu_point_root').id},
|
||||
}
|
||||
return True
|
||||
|
||||
def _close_session_action(self, amount_to_balance):
|
||||
default_account = self._get_balancing_account()
|
||||
@@ -452,6 +448,8 @@ class PosSession(models.Model):
|
||||
data = {}
|
||||
data = self._accumulate_amounts(data)
|
||||
data = self._create_non_reconciliable_move_lines(data)
|
||||
data = self._create_bank_payment_moves(data)
|
||||
data = self._create_pay_later_receivable_lines(data)
|
||||
data = self._create_cash_statement_lines_and_cash_move_lines(data)
|
||||
data = self._create_invoice_receivable_lines(data)
|
||||
data = self._create_stock_output_lines(data)
|
||||
@@ -463,55 +461,78 @@ class PosSession(models.Model):
|
||||
def _accumulate_amounts(self, data):
|
||||
# Accumulate the amounts for each accounting lines group
|
||||
# Each dict maps `key` -> `amounts`, where `key` is the group key.
|
||||
# E.g. `combine_receivables` is derived from pos.payment records
|
||||
# E.g. `combine_receivables_bank` is derived from pos.payment records
|
||||
# in the self.order_ids with group key of the `payment_method_id`
|
||||
# field of the pos.payment record.
|
||||
amounts = lambda: {'amount': 0.0, 'amount_converted': 0.0}
|
||||
tax_amounts = lambda: {'amount': 0.0, 'amount_converted': 0.0, 'base_amount': 0.0, 'base_amount_converted': 0.0}
|
||||
split_receivables = defaultdict(amounts)
|
||||
split_receivables_bank = defaultdict(amounts)
|
||||
split_receivables_cash = defaultdict(amounts)
|
||||
combine_receivables = defaultdict(amounts)
|
||||
split_receivables_pay_later = defaultdict(amounts)
|
||||
combine_receivables_bank = defaultdict(amounts)
|
||||
combine_receivables_cash = defaultdict(amounts)
|
||||
invoice_receivables = defaultdict(amounts)
|
||||
combine_receivables_pay_later = defaultdict(amounts)
|
||||
combine_invoice_receivables = defaultdict(amounts)
|
||||
split_invoice_receivables = defaultdict(amounts)
|
||||
sales = defaultdict(amounts)
|
||||
taxes = defaultdict(tax_amounts)
|
||||
stock_expense = defaultdict(amounts)
|
||||
stock_return = defaultdict(amounts)
|
||||
stock_output = defaultdict(amounts)
|
||||
rounding_difference = {'amount': 0.0, 'amount_converted': 0.0}
|
||||
# Track the receivable lines of the invoiced orders' account moves for reconciliation
|
||||
# Track the receivable lines of the order's invoice payment moves for reconciliation
|
||||
# These receivable lines are reconciled to the corresponding invoice receivable lines
|
||||
# of this session's move_id.
|
||||
order_account_move_receivable_lines = defaultdict(lambda: self.env['account.move.line'])
|
||||
combine_inv_payment_receivable_lines = defaultdict(lambda: self.env['account.move.line'])
|
||||
split_inv_payment_receivable_lines = defaultdict(lambda: self.env['account.move.line'])
|
||||
rounded_globally = self.company_id.tax_calculation_rounding_method == 'round_globally'
|
||||
pos_receivable_account = self.company_id.account_default_pos_receivable_account_id
|
||||
currency_rounding = self.currency_id.rounding
|
||||
for order in self.order_ids:
|
||||
# Combine pos receivable lines
|
||||
# Separate cash payments for cash reconciliation later.
|
||||
order_is_invoiced = order.is_invoiced
|
||||
for payment in order.payment_ids:
|
||||
amount, date = payment.amount, payment.payment_date
|
||||
if payment.payment_method_id.split_transactions:
|
||||
if payment.payment_method_id.is_cash_count:
|
||||
split_receivables_cash[payment] = self._update_amounts(split_receivables_cash[payment], {'amount': amount}, date)
|
||||
else:
|
||||
split_receivables[payment] = self._update_amounts(split_receivables[payment], {'amount': amount}, date)
|
||||
else:
|
||||
key = payment.payment_method_id
|
||||
if payment.payment_method_id.is_cash_count:
|
||||
combine_receivables_cash[key] = self._update_amounts(combine_receivables_cash[key], {'amount': amount}, date)
|
||||
else:
|
||||
combine_receivables[key] = self._update_amounts(combine_receivables[key], {'amount': amount}, date)
|
||||
amount = payment.amount
|
||||
if float_is_zero(amount, precision_rounding=currency_rounding):
|
||||
continue
|
||||
date = payment.payment_date
|
||||
payment_method = payment.payment_method_id
|
||||
is_split_payment = payment.payment_method_id.split_transactions
|
||||
payment_type = payment_method.type
|
||||
|
||||
if order.is_invoiced:
|
||||
# Combine invoice receivable lines
|
||||
key = order.partner_id
|
||||
if self.config_id.cash_rounding:
|
||||
invoice_receivables[key] = self._update_amounts(invoice_receivables[key], {'amount': order.amount_paid}, order.date_order)
|
||||
else:
|
||||
invoice_receivables[key] = self._update_amounts(invoice_receivables[key], {'amount': order.amount_total}, order.date_order)
|
||||
# side loop to gather receivable lines by account for reconciliation
|
||||
for move_line in order.account_move.line_ids.filtered(lambda aml: aml.account_id.internal_type == 'receivable' and not aml.reconciled):
|
||||
order_account_move_receivable_lines[move_line.account_id.id] |= move_line
|
||||
else:
|
||||
# If not pay_later, we create the receivable vals for both invoiced and uninvoiced orders.
|
||||
# Separate the split and aggregated payments.
|
||||
# Moreover, if the order is invoiced, we create the pos receivable vals that will balance the
|
||||
# pos receivable lines from the invoice payments.
|
||||
if payment_type != 'pay_later':
|
||||
if is_split_payment and payment_type == 'cash':
|
||||
split_receivables_cash[payment] = self._update_amounts(split_receivables_cash[payment], {'amount': amount}, date)
|
||||
elif not is_split_payment and payment_type == 'cash':
|
||||
combine_receivables_cash[payment_method] = self._update_amounts(combine_receivables_cash[payment_method], {'amount': amount}, date)
|
||||
elif is_split_payment and payment_type == 'bank':
|
||||
split_receivables_bank[payment] = self._update_amounts(split_receivables_bank[payment], {'amount': amount}, date)
|
||||
elif not is_split_payment and payment_type == 'bank':
|
||||
combine_receivables_bank[payment_method] = self._update_amounts(combine_receivables_bank[payment_method], {'amount': amount}, date)
|
||||
|
||||
# Create the vals to create the pos receivables that will balance the pos receivables from invoice payment moves.
|
||||
if order_is_invoiced:
|
||||
if is_split_payment:
|
||||
split_inv_payment_receivable_lines[payment] |= payment.account_move_id.line_ids.filtered(lambda line: line.account_id == pos_receivable_account)
|
||||
split_invoice_receivables[payment] = self._update_amounts(split_invoice_receivables[payment], {'amount': payment.amount}, order.date_order)
|
||||
else:
|
||||
combine_inv_payment_receivable_lines[payment_method] |= payment.account_move_id.line_ids.filtered(lambda line: line.account_id == pos_receivable_account)
|
||||
combine_invoice_receivables[payment_method] = self._update_amounts(combine_invoice_receivables[payment_method], {'amount': payment.amount}, order.date_order)
|
||||
|
||||
# If pay_later, we create the receivable lines.
|
||||
# if split, with partner
|
||||
# Otherwise, it's aggregated (combined)
|
||||
# But only do if order is *not* invoiced because no account move is created for pay later invoice payments.
|
||||
if payment_type == 'pay_later' and not order_is_invoiced:
|
||||
if is_split_payment:
|
||||
split_receivables_pay_later[payment] = self._update_amounts(split_receivables_pay_later[payment], {'amount': amount}, date)
|
||||
elif not is_split_payment:
|
||||
combine_receivables_pay_later[payment_method] = self._update_amounts(combine_receivables_pay_later[payment_method], {'amount': amount}, date)
|
||||
|
||||
if not order_is_invoiced:
|
||||
order_taxes = defaultdict(tax_amounts)
|
||||
for order_line in order.lines:
|
||||
line = self._prepare_line(order_line)
|
||||
@@ -589,16 +610,20 @@ class PosSession(models.Model):
|
||||
'taxes': taxes,
|
||||
'sales': sales,
|
||||
'stock_expense': stock_expense,
|
||||
'split_receivables': split_receivables,
|
||||
'combine_receivables': combine_receivables,
|
||||
'split_receivables_bank': split_receivables_bank,
|
||||
'combine_receivables_bank': combine_receivables_bank,
|
||||
'split_receivables_cash': split_receivables_cash,
|
||||
'combine_receivables_cash': combine_receivables_cash,
|
||||
'invoice_receivables': invoice_receivables,
|
||||
'combine_invoice_receivables': combine_invoice_receivables,
|
||||
'split_receivables_pay_later': split_receivables_pay_later,
|
||||
'combine_receivables_pay_later': combine_receivables_pay_later,
|
||||
'stock_return': stock_return,
|
||||
'stock_output': stock_output,
|
||||
'order_account_move_receivable_lines': order_account_move_receivable_lines,
|
||||
'combine_inv_payment_receivable_lines': combine_inv_payment_receivable_lines,
|
||||
'rounding_difference': rounding_difference,
|
||||
'MoveLine': MoveLine
|
||||
'MoveLine': MoveLine,
|
||||
'split_invoice_receivables': split_invoice_receivables,
|
||||
'split_inv_payment_receivable_lines': split_inv_payment_receivable_lines,
|
||||
})
|
||||
return data
|
||||
|
||||
@@ -612,8 +637,6 @@ class PosSession(models.Model):
|
||||
taxes = data.get('taxes')
|
||||
sales = data.get('sales')
|
||||
stock_expense = data.get('stock_expense')
|
||||
split_receivables = data.get('split_receivables')
|
||||
combine_receivables = data.get('combine_receivables')
|
||||
rounding_difference = data.get('rounding_difference')
|
||||
MoveLine = data.get('MoveLine')
|
||||
|
||||
@@ -635,12 +658,74 @@ class PosSession(models.Model):
|
||||
tax_vals
|
||||
+ [self._get_sale_vals(key, amounts['amount'], amounts['amount_converted']) for key, amounts in sales.items()]
|
||||
+ [self._get_stock_expense_vals(key, amounts['amount'], amounts['amount_converted']) for key, amounts in stock_expense.items()]
|
||||
+ [self._get_split_receivable_vals(key, amounts['amount'], amounts['amount_converted']) for key, amounts in split_receivables.items()]
|
||||
+ [self._get_combine_receivable_vals(key, amounts['amount'], amounts['amount_converted']) for key, amounts in combine_receivables.items()]
|
||||
+ rounding_vals
|
||||
)
|
||||
return data
|
||||
|
||||
def _create_bank_payment_moves(self, data):
|
||||
combine_receivables_bank = data.get('combine_receivables_bank')
|
||||
split_receivables_bank = data.get('split_receivables_bank')
|
||||
MoveLine = data.get('MoveLine')
|
||||
payment_method_to_receivable_lines = {}
|
||||
payment_to_receivable_lines = {}
|
||||
for payment_method, amounts in combine_receivables_bank.items():
|
||||
combine_receivable_line = MoveLine.create(self._get_combine_receivable_vals(payment_method, amounts['amount'], amounts['amount_converted']))
|
||||
payment_receivable_line = self._create_combine_account_payment(payment_method, amounts)
|
||||
payment_method_to_receivable_lines[payment_method] = combine_receivable_line | payment_receivable_line
|
||||
|
||||
for payment, amounts in split_receivables_bank.items():
|
||||
split_receivable_line = MoveLine.create(self._get_split_receivable_vals(payment, amounts['amount'], amounts['amount_converted']))
|
||||
payment_receivable_line = self._create_split_account_payment(payment, amounts)
|
||||
payment_to_receivable_lines[payment] = split_receivable_line | payment_receivable_line
|
||||
|
||||
data['payment_method_to_receivable_lines'] = payment_method_to_receivable_lines
|
||||
data['payment_to_receivable_lines'] = payment_to_receivable_lines
|
||||
return data
|
||||
|
||||
def _create_pay_later_receivable_lines(self, data):
|
||||
MoveLine = data.get('MoveLine')
|
||||
combine_receivables_pay_later = data.get('combine_receivables_pay_later')
|
||||
split_receivables_pay_later = data.get('split_receivables_pay_later')
|
||||
vals = []
|
||||
for payment_method, amounts in combine_receivables_pay_later.items():
|
||||
vals.append(self._get_combine_receivable_vals(payment_method, amounts['amount'], amounts['amount_converted']))
|
||||
for payment, amounts in split_receivables_pay_later.items():
|
||||
vals.append(self._get_split_receivable_vals(payment, amounts['amount'], amounts['amount_converted']))
|
||||
MoveLine.create(vals)
|
||||
return data
|
||||
|
||||
def _create_combine_account_payment(self, payment_method, amounts):
|
||||
outstanding_account = payment_method.outstanding_account_id or self.company_id.account_journal_payment_debit_account_id
|
||||
destination_account = self._get_receivable_account(payment_method)
|
||||
account_payment = self.env['account.payment'].create({
|
||||
'amount': amounts['amount'],
|
||||
'journal_id': payment_method.journal_id.id,
|
||||
'force_outstanding_account_id': outstanding_account.id,
|
||||
'destination_account_id': destination_account.id,
|
||||
'ref': _('Combine %s POS payments from %s') % (payment_method.name, self.name),
|
||||
'pos_payment_method_id': payment_method.id,
|
||||
'pos_session_id': self.id,
|
||||
})
|
||||
account_payment.action_post()
|
||||
return account_payment.move_id.line_ids.filtered(lambda line: line.account_id == account_payment.destination_account_id)
|
||||
|
||||
def _create_split_account_payment(self, payment, amounts):
|
||||
payment_method = payment.payment_method_id
|
||||
if not payment_method.journal_id:
|
||||
return self.env['account.move.line']
|
||||
outstanding_account = payment_method.outstanding_account_id or self.company_id.account_journal_payment_debit_account_id
|
||||
account_payment = self.env['account.payment'].create({
|
||||
'amount': amounts['amount'],
|
||||
'partner_id': payment.partner_id.id,
|
||||
'journal_id': payment_method.journal_id.id,
|
||||
'force_outstanding_account_id': outstanding_account.id,
|
||||
'ref': _('%s POS payment of %s in %s') % (payment_method.name, payment.partner_id.display_name, self.name),
|
||||
'pos_payment_method_id': payment_method.id,
|
||||
'pos_session_id': self.id,
|
||||
})
|
||||
account_payment.action_post()
|
||||
return account_payment.move_id.line_ids.filtered(lambda line: line.account_id == account_payment.destination_account_id)
|
||||
|
||||
def _create_cash_statement_lines_and_cash_move_lines(self, data):
|
||||
# Create the split and combine cash statement lines and account move lines.
|
||||
# Keep the reference by statement for reconciliation.
|
||||
@@ -657,16 +742,16 @@ class PosSession(models.Model):
|
||||
split_cash_statement_line_vals = defaultdict(list)
|
||||
split_cash_receivable_vals = defaultdict(list)
|
||||
for payment, amounts in split_receivables_cash.items():
|
||||
statement = statements_by_journal_id[payment.payment_method_id.cash_journal_id.id]
|
||||
split_cash_statement_line_vals[statement].append(self._get_statement_line_vals(statement, payment.payment_method_id.receivable_account_id, amounts['amount'], date=payment.payment_date, partner=payment.pos_order_id.partner_id))
|
||||
statement = statements_by_journal_id[payment.payment_method_id.journal_id.id]
|
||||
split_cash_statement_line_vals[statement].append(self._get_split_statement_line_vals(statement, amounts['amount'], payment))
|
||||
split_cash_receivable_vals[statement].append(self._get_split_receivable_vals(payment, amounts['amount'], amounts['amount_converted']))
|
||||
# handle combine cash payments
|
||||
combine_cash_statement_line_vals = defaultdict(list)
|
||||
combine_cash_receivable_vals = defaultdict(list)
|
||||
for payment_method, amounts in combine_receivables_cash.items():
|
||||
if not float_is_zero(amounts['amount'] , precision_rounding=self.currency_id.rounding):
|
||||
statement = statements_by_journal_id[payment_method.cash_journal_id.id]
|
||||
combine_cash_statement_line_vals[statement].append(self._get_statement_line_vals(statement, payment_method.receivable_account_id, amounts['amount']))
|
||||
statement = statements_by_journal_id[payment_method.journal_id.id]
|
||||
combine_cash_statement_line_vals[statement].append(self._get_combine_statement_line_vals(statement, amounts['amount'], payment_method))
|
||||
combine_cash_receivable_vals[statement].append(self._get_combine_receivable_vals(payment_method, amounts['amount'], amounts['amount_converted']))
|
||||
# create the statement lines and account move lines
|
||||
BankStatementLine = self.env['account.bank.statement.line']
|
||||
@@ -675,8 +760,8 @@ class PosSession(models.Model):
|
||||
split_cash_receivable_lines = {}
|
||||
combine_cash_receivable_lines = {}
|
||||
for statement in self.statement_ids:
|
||||
split_cash_statement_lines[statement] = BankStatementLine.create(split_cash_statement_line_vals[statement])
|
||||
combine_cash_statement_lines[statement] = BankStatementLine.create(combine_cash_statement_line_vals[statement])
|
||||
split_cash_statement_lines[statement] = BankStatementLine.create(split_cash_statement_line_vals[statement]).mapped('move_id.line_ids').filtered(lambda line: line.account_id.internal_type == 'receivable')
|
||||
combine_cash_statement_lines[statement] = BankStatementLine.create(combine_cash_statement_line_vals[statement]).mapped('move_id.line_ids').filtered(lambda line: line.account_id.internal_type == 'receivable')
|
||||
split_cash_receivable_lines[statement] = MoveLine.create(split_cash_receivable_vals[statement])
|
||||
combine_cash_receivable_lines[statement] = MoveLine.create(combine_cash_receivable_vals[statement])
|
||||
|
||||
@@ -691,27 +776,28 @@ class PosSession(models.Model):
|
||||
def _create_invoice_receivable_lines(self, data):
|
||||
# Create invoice receivable lines for this session's move_id.
|
||||
# Keep reference of the invoice receivable lines because
|
||||
# they are reconciled with the lines in order_account_move_receivable_lines
|
||||
# they are reconciled with the lines in combine_inv_payment_receivable_lines
|
||||
MoveLine = data.get('MoveLine')
|
||||
invoice_receivables = data.get('invoice_receivables')
|
||||
combine_invoice_receivables = data.get('combine_invoice_receivables')
|
||||
split_invoice_receivables = data.get('split_invoice_receivables')
|
||||
|
||||
invoice_receivable_vals = defaultdict(list)
|
||||
invoice_receivable_lines = {}
|
||||
for partner, amounts in invoice_receivables.items():
|
||||
commercial_partner = partner.commercial_partner_id
|
||||
account_id = commercial_partner.property_account_receivable_id.id
|
||||
invoice_receivable_vals[commercial_partner].append(self._get_invoice_receivable_vals(account_id, amounts['amount'], amounts['amount_converted'], partner=commercial_partner))
|
||||
for commercial_partner, vals in invoice_receivable_vals.items():
|
||||
account_id = commercial_partner.property_account_receivable_id.id
|
||||
combine_invoice_receivable_vals = defaultdict(list)
|
||||
split_invoice_receivable_vals = defaultdict(list)
|
||||
combine_invoice_receivable_lines = {}
|
||||
split_invoice_receivable_lines = {}
|
||||
for payment_method, amounts in combine_invoice_receivables.items():
|
||||
combine_invoice_receivable_vals[payment_method].append(self._get_invoice_receivable_vals(amounts['amount'], amounts['amount_converted']))
|
||||
for payment, amounts in split_invoice_receivables.items():
|
||||
split_invoice_receivable_vals[payment].append(self._get_invoice_receivable_vals(amounts['amount'], amounts['amount_converted']))
|
||||
for payment_method, vals in combine_invoice_receivable_vals.items():
|
||||
receivable_lines = MoveLine.create(vals)
|
||||
for receivable_line in receivable_lines:
|
||||
if (not receivable_line.reconciled):
|
||||
if account_id not in invoice_receivable_lines:
|
||||
invoice_receivable_lines[account_id] = receivable_line
|
||||
else:
|
||||
invoice_receivable_lines[account_id] |= receivable_line
|
||||
combine_invoice_receivable_lines[payment_method] = receivable_lines
|
||||
for payment, vals in split_invoice_receivable_vals.items():
|
||||
receivable_lines = MoveLine.create(vals)
|
||||
split_invoice_receivable_lines[payment] = receivable_lines
|
||||
|
||||
data.update({'invoice_receivable_lines': invoice_receivable_lines})
|
||||
data.update({'combine_invoice_receivable_lines': combine_invoice_receivable_lines})
|
||||
data.update({'split_invoice_receivable_lines': split_invoice_receivable_lines})
|
||||
return data
|
||||
|
||||
def _create_stock_output_lines(self, data):
|
||||
@@ -739,22 +825,26 @@ class PosSession(models.Model):
|
||||
combine_cash_statement_lines = data.get('combine_cash_statement_lines')
|
||||
split_cash_receivable_lines = data.get('split_cash_receivable_lines')
|
||||
combine_cash_receivable_lines = data.get('combine_cash_receivable_lines')
|
||||
order_account_move_receivable_lines = data.get('order_account_move_receivable_lines')
|
||||
invoice_receivable_lines = data.get('invoice_receivable_lines')
|
||||
combine_inv_payment_receivable_lines = data.get('combine_inv_payment_receivable_lines')
|
||||
split_inv_payment_receivable_lines = data.get('split_inv_payment_receivable_lines')
|
||||
combine_invoice_receivable_lines = data.get('combine_invoice_receivable_lines')
|
||||
split_invoice_receivable_lines = data.get('split_invoice_receivable_lines')
|
||||
stock_output_lines = data.get('stock_output_lines')
|
||||
payment_method_to_receivable_lines = data.get('payment_method_to_receivable_lines')
|
||||
payment_to_receivable_lines = data.get('payment_to_receivable_lines')
|
||||
|
||||
for statement in self.statement_ids:
|
||||
if not self.config_id.cash_control:
|
||||
statement.write({'balance_end_real': statement.balance_end})
|
||||
statement.button_post()
|
||||
all_lines = (
|
||||
split_cash_statement_lines[statement].mapped('move_id.line_ids').filtered(lambda aml: aml.account_id.internal_type == 'receivable')
|
||||
| combine_cash_statement_lines[statement].mapped('move_id.line_ids').filtered(lambda aml: aml.account_id.internal_type == 'receivable')
|
||||
split_cash_statement_lines[statement]
|
||||
| combine_cash_statement_lines[statement]
|
||||
| split_cash_receivable_lines[statement]
|
||||
| combine_cash_receivable_lines[statement]
|
||||
)
|
||||
accounts = all_lines.mapped('account_id')
|
||||
lines_by_account = [all_lines.filtered(lambda l: l.account_id == account and not l.reconciled) for account in accounts]
|
||||
lines_by_account = [all_lines.filtered(lambda l: l.account_id == account and not l.reconciled) for account in accounts if account.reconcile]
|
||||
for lines in lines_by_account:
|
||||
lines.reconcile()
|
||||
# We try to validate the statement after the reconciliation is done
|
||||
@@ -769,11 +859,26 @@ class PosSession(models.Model):
|
||||
except UserError:
|
||||
pass
|
||||
|
||||
# reconcile invoice receivable lines
|
||||
for account_id in order_account_move_receivable_lines:
|
||||
( order_account_move_receivable_lines[account_id]
|
||||
| invoice_receivable_lines.get(account_id, self.env['account.move.line'])
|
||||
).reconcile()
|
||||
for payment_method, lines in payment_method_to_receivable_lines.items():
|
||||
receivable_account = self._get_receivable_account(payment_method)
|
||||
if receivable_account.reconcile:
|
||||
lines.filtered(lambda line: not line.reconciled).reconcile()
|
||||
|
||||
for payment, lines in payment_to_receivable_lines.items():
|
||||
if payment.partner_id.property_account_receivable_id.reconcile:
|
||||
lines.filtered(lambda line: not line.reconciled).reconcile()
|
||||
|
||||
# Reconcile invoice payments' receivable lines. But we only do when the account is reconcilable.
|
||||
# Though `account_default_pos_receivable_account_id` should be of type receivable, there is currently
|
||||
# no constraint for it. Therefore, it is possible to put set a non-reconcilable account to it.
|
||||
if self.company_id.account_default_pos_receivable_account_id.reconcile:
|
||||
for payment_method in combine_inv_payment_receivable_lines:
|
||||
lines = combine_inv_payment_receivable_lines[payment_method] | combine_invoice_receivable_lines.get(payment_method, self.env['account.move.line'])
|
||||
lines.filtered(lambda line: not line.reconciled).reconcile()
|
||||
|
||||
for payment in split_inv_payment_receivable_lines:
|
||||
lines = split_inv_payment_receivable_lines[payment] | split_invoice_receivable_lines.get(payment, self.env['account.move.line'])
|
||||
lines.filtered(lambda line: not line.reconciled).reconcile()
|
||||
|
||||
# reconcile stock output lines
|
||||
pickings = self.picking_ids.filtered(lambda p: not p.pos_order_id)
|
||||
@@ -842,29 +947,28 @@ class PosSession(models.Model):
|
||||
return self._credit_amounts(partial_args, amount, amount_converted)
|
||||
|
||||
def _get_split_receivable_vals(self, payment, amount, amount_converted):
|
||||
accounting_partner = self.env["res.partner"]._find_accounting_partner(payment.partner_id)
|
||||
partial_vals = {
|
||||
'account_id': payment.payment_method_id.receivable_account_id.id,
|
||||
'account_id': accounting_partner.property_account_receivable_id.id,
|
||||
'move_id': self.move_id.id,
|
||||
'partner_id': self.env["res.partner"]._find_accounting_partner(payment.partner_id).id,
|
||||
'partner_id': accounting_partner.id,
|
||||
'name': '%s - %s' % (self.name, payment.payment_method_id.name),
|
||||
}
|
||||
return self._debit_amounts(partial_vals, amount, amount_converted)
|
||||
|
||||
def _get_combine_receivable_vals(self, payment_method, amount, amount_converted):
|
||||
partial_vals = {
|
||||
'account_id': payment_method.receivable_account_id.id,
|
||||
'account_id': self._get_receivable_account(payment_method).id,
|
||||
'move_id': self.move_id.id,
|
||||
'name': '%s - %s' % (self.name, payment_method.name)
|
||||
}
|
||||
return self._debit_amounts(partial_vals, amount, amount_converted)
|
||||
|
||||
def _get_invoice_receivable_vals(self, account_id, amount, amount_converted, **kwargs):
|
||||
partner = kwargs.get('partner', False)
|
||||
def _get_invoice_receivable_vals(self, amount, amount_converted):
|
||||
partial_vals = {
|
||||
'account_id': account_id,
|
||||
'account_id': self.company_id.account_default_pos_receivable_account_id.id,
|
||||
'move_id': self.move_id.id,
|
||||
'name': 'From invoiced orders',
|
||||
'partner_id': partner and partner.id or False,
|
||||
'name': _('From invoice payments'),
|
||||
}
|
||||
return self._credit_amounts(partial_vals, amount, amount_converted)
|
||||
|
||||
@@ -906,15 +1010,26 @@ class PosSession(models.Model):
|
||||
partial_args = {'account_id': out_account.id, 'move_id': self.move_id.id}
|
||||
return self._credit_amounts(partial_args, amount, amount_converted, force_company_currency=True)
|
||||
|
||||
def _get_statement_line_vals(self, statement, receivable_account, amount, date=False, partner=False):
|
||||
def _get_combine_statement_line_vals(self, statement, amount, payment_method):
|
||||
return {
|
||||
'date': fields.Date.context_today(self, timestamp=date),
|
||||
'date': fields.Date.context_today(self),
|
||||
'amount': amount,
|
||||
'payment_ref': self.name,
|
||||
'statement_id': statement.id,
|
||||
'journal_id': statement.journal_id.id,
|
||||
'counterpart_account_id': receivable_account.id,
|
||||
'partner_id': partner and self.env["res.partner"]._find_accounting_partner(partner).id
|
||||
'counterpart_account_id': self._get_receivable_account(payment_method).id,
|
||||
}
|
||||
|
||||
def _get_split_statement_line_vals(self, statement, amount, payment):
|
||||
accounting_partner = self.env["res.partner"]._find_accounting_partner(payment.partner_id)
|
||||
return {
|
||||
'date': fields.Date.context_today(self, timestamp=payment.payment_date),
|
||||
'amount': amount,
|
||||
'payment_ref': self.name,
|
||||
'statement_id': statement.id,
|
||||
'journal_id': statement.journal_id.id,
|
||||
'counterpart_account_id': accounting_partner.property_account_receivable_id.id,
|
||||
'partner_id': accounting_partner.id,
|
||||
}
|
||||
|
||||
def _update_amounts(self, old_amounts, amounts_to_add, date, round=True, force_company_currency=False):
|
||||
@@ -1063,27 +1178,17 @@ class PosSession(models.Model):
|
||||
}
|
||||
|
||||
def _get_related_account_moves(self):
|
||||
def get_matched_move_lines(aml):
|
||||
if aml.credit > 0:
|
||||
return [r.debit_move_id.id for r in aml.matched_debit_ids]
|
||||
else:
|
||||
return [r.credit_move_id.id for r in aml.matched_credit_ids]
|
||||
pickings = self.picking_ids | self.order_ids.mapped('picking_ids')
|
||||
invoices = self.mapped('order_ids.account_move')
|
||||
invoice_payments = self.mapped('order_ids.payment_ids.account_move_id')
|
||||
stock_account_moves = pickings.mapped('move_lines.account_move_ids')
|
||||
cash_moves = self.cash_register_id.line_ids.mapped('move_id')
|
||||
bank_payment_moves = self.bank_payment_ids.mapped('move_id')
|
||||
return invoices | invoice_payments | self.move_id | stock_account_moves | cash_moves | bank_payment_moves
|
||||
|
||||
session_move = self.move_id
|
||||
# get all the linked move lines to this account move.
|
||||
non_reconcilable_lines = session_move.line_ids.filtered(lambda aml: not aml.account_id.reconcile)
|
||||
reconcilable_lines = session_move.line_ids - non_reconcilable_lines
|
||||
fully_reconciled_lines = reconcilable_lines.filtered(lambda aml: aml.full_reconcile_id)
|
||||
partially_reconciled_lines = reconcilable_lines - fully_reconciled_lines
|
||||
|
||||
cash_move_lines = self.env['account.move.line'].search([('statement_id', '=', self.cash_register_id.id)])
|
||||
|
||||
ids = (non_reconcilable_lines.ids
|
||||
+ fully_reconciled_lines.mapped('full_reconcile_id').mapped('reconciled_line_ids').ids
|
||||
+ sum(partially_reconciled_lines.mapped(get_matched_move_lines), partially_reconciled_lines.ids)
|
||||
+ cash_move_lines.ids)
|
||||
|
||||
return self.env['account.move.line'].browse(ids).mapped('move_id')
|
||||
def _get_receivable_account(self, payment_method):
|
||||
"""Returns the default pos receivable account if no receivable_account_id is set on the payment method."""
|
||||
return payment_method.receivable_account_id or self.company_id.account_default_pos_receivable_account_id
|
||||
|
||||
def action_show_payments_list(self):
|
||||
return {
|
||||
|
||||
@@ -12,6 +12,7 @@ class ResConfigSettings(models.TransientModel):
|
||||
module_pos_six = fields.Boolean(string="Six Payment Terminal", help="The transactions are processed by Six. Set the IP address of the terminal on the related payment method.")
|
||||
update_stock_quantities = fields.Selection(related="company_id.point_of_sale_update_stock_quantities", readonly=False)
|
||||
module_pos_coupon = fields.Boolean("Coupon and Promotion Programs", help="Allow the use of coupon and promotion programs in PoS.")
|
||||
account_default_pos_receivable_account_id = fields.Many2one(string='Default Account Receivable (PoS)', related='company_id.account_default_pos_receivable_account_id', readonly=False)
|
||||
|
||||
def set_values(self):
|
||||
super(ResConfigSettings, self).set_values()
|
||||
|
||||
@@ -1511,6 +1511,10 @@ td {
|
||||
flex-grow: 1;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
/* make the name of the payment line to be truncated with ellipsis */
|
||||
white-space: nowrap;
|
||||
overflow:hidden;
|
||||
text-overflow:ellipsis;
|
||||
}
|
||||
.paymentline .payment-amount {
|
||||
margin-left: 10px;
|
||||
|
||||
+12
-4
@@ -42,11 +42,19 @@ odoo.define('point_of_sale.InvoiceButton', function (require) {
|
||||
}
|
||||
async _downloadInvoice(orderId) {
|
||||
try {
|
||||
await this.env.pos.do_action('point_of_sale.pos_invoice_report', {
|
||||
additional_context: {
|
||||
active_ids: [orderId],
|
||||
},
|
||||
const [orderWithInvoice] = await this.rpc({
|
||||
method: 'read',
|
||||
model: 'pos.order',
|
||||
args: [orderId, ['account_move']],
|
||||
kwargs: { load: false },
|
||||
});
|
||||
if (orderWithInvoice && orderWithInvoice.account_move) {
|
||||
await this.env.pos.do_action('account.account_invoices', {
|
||||
additional_context: {
|
||||
active_ids: [orderWithInvoice.account_move],
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
throw error;
|
||||
|
||||
@@ -227,16 +227,29 @@ odoo.define('point_of_sale.PaymentScreen', function (require) {
|
||||
return !this.error? 'ReceiptScreen' : 'ProductScreen';
|
||||
}
|
||||
async _isOrderValid(isForceValidate) {
|
||||
if (this.currentOrder.get_orderlines().length === 0) {
|
||||
if (this.currentOrder.get_orderlines().length === 0 && this.currentOrder.is_to_invoice()) {
|
||||
this.showPopup('ErrorPopup', {
|
||||
title: this.env._t('Empty Order'),
|
||||
body: this.env._t(
|
||||
'There must be at least one product in your order before it can be validated'
|
||||
'There must be at least one product in your order before it can be validated and invoiced.'
|
||||
),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
const splitPayments = this.paymentLines.filter(payment => payment.payment_method.split_transactions)
|
||||
if (splitPayments.length && !this.currentOrder.get_client()) {
|
||||
const paymentMethod = splitPayments[0].payment_method
|
||||
const { confirmed } = await this.showPopup('ConfirmPopup', {
|
||||
title: this.env._t('Customer Required'),
|
||||
body: _.str.sprintf(this.env._t('Customer is required for %s payment method.'), paymentMethod.name),
|
||||
});
|
||||
if (confirmed) {
|
||||
this.selectClient();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((this.currentOrder.is_to_invoice() || this.currentOrder.is_to_ship()) && !this.currentOrder.get_client()) {
|
||||
const { confirmed } = await this.showPopup('ConfirmPopup', {
|
||||
title: this.env._t('Please select the Customer'),
|
||||
|
||||
@@ -512,7 +512,7 @@ exports.PosModel = Backbone.Model.extend({
|
||||
}
|
||||
},{
|
||||
model: 'pos.payment.method',
|
||||
fields: ['name', 'is_cash_count', 'use_payment_terminal'],
|
||||
fields: ['name', 'is_cash_count', 'use_payment_terminal', 'split_transactions', 'type'],
|
||||
domain: function(self){return ['|',['active', '=', false], ['active', '=', true]]; },
|
||||
loaded: function(self, payment_methods) {
|
||||
self.payment_methods = payment_methods.sort(function(a,b){
|
||||
@@ -1000,18 +1000,29 @@ exports.PosModel = Backbone.Model.extend({
|
||||
});
|
||||
|
||||
// on success, get the order id generated by the server
|
||||
transfer.then(function(order_server_id){
|
||||
transfer.then(async function(order_server_id){
|
||||
// generate the pdf and download it
|
||||
if (order_server_id.length) {
|
||||
self.do_action('point_of_sale.pos_invoice_report',{additional_context:{
|
||||
active_ids:order_server_id,
|
||||
}}).then(function () {
|
||||
resolveInvoiced(order_server_id);
|
||||
resolveDone();
|
||||
}).guardedCatch(function (error) {
|
||||
const [orderWithInvoice] = await self.rpc({
|
||||
method: 'read',
|
||||
model: 'pos.order',
|
||||
args: [order_server_id, ['account_move']],
|
||||
kwargs: { load: false },
|
||||
});
|
||||
if (orderWithInvoice && orderWithInvoice.account_move) {
|
||||
self.do_action('account.account_invoices',{additional_context:{
|
||||
active_ids:[orderWithInvoice.account_move],
|
||||
}}).then(function () {
|
||||
resolveInvoiced(order_server_id);
|
||||
resolveDone();
|
||||
}).catch(error => {
|
||||
rejectInvoiced({code:401, message:'Backend Invoice', data:{order: order}});
|
||||
rejectDone();
|
||||
});
|
||||
} else {
|
||||
rejectInvoiced({code:401, message:'Backend Invoice', data:{order: order}});
|
||||
rejectDone();
|
||||
});
|
||||
}
|
||||
} else if (order_server_id.length) {
|
||||
resolveInvoiced(order_server_id);
|
||||
resolveDone();
|
||||
|
||||
@@ -10,15 +10,6 @@
|
||||
<field name="binding_type">report</field>
|
||||
</record>
|
||||
|
||||
<!-- used from POS UI, no need to be in print menu -->
|
||||
<record id="pos_invoice_report" model="ir.actions.report">
|
||||
<field name="name">Invoice</field>
|
||||
<field name="model">pos.order</field>
|
||||
<field name="report_type">qweb-pdf</field>
|
||||
<field name="report_name">point_of_sale.report_invoice</field>
|
||||
<field name="print_report_name">'Invoice - %s' % (object.name)</field>
|
||||
</record>
|
||||
|
||||
<record id="sale_details_report" model="ir.actions.report">
|
||||
<field name="name">Sales Details</field>
|
||||
<field name="model">report.point_of_sale.report_saledetails</field>
|
||||
@@ -26,5 +17,4 @@
|
||||
<field name="report_name">point_of_sale.report_saledetails</field>
|
||||
</record>
|
||||
|
||||
<template id="report_invoice" inherit_id="account.report_invoice" primary="True"/>
|
||||
</odoo>
|
||||
|
||||
@@ -408,7 +408,7 @@
|
||||
Payment methods available
|
||||
</div>
|
||||
<div class="content-group mt16">
|
||||
<field name="payment_method_ids" colspan="4" nolabel="1" widget="many2many_tags" required="1" attrs="{'readonly': [('has_active_session','=', True)]}"/>
|
||||
<field name="payment_method_ids" colspan="4" nolabel="1" widget="many2many_tags" required="1" attrs="{'readonly': [('has_active_session','=', True)]}" options="{'no_create': True}" />
|
||||
</div>
|
||||
<div>
|
||||
<button name="%(action_payment_methods_tree)d" icon="fa-arrow-right" type="action" string="Payment Methods" class="btn-link"/>
|
||||
|
||||
@@ -8,21 +8,35 @@
|
||||
<sheet>
|
||||
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="type" invisible="1" />
|
||||
<div class="oe_title">
|
||||
<label for="name"/>
|
||||
<h1><field name="name" placeholder="e.g. Cash" class="oe_inline"/></h1>
|
||||
</div>
|
||||
<div class="o_notification_alert alert alert-warning" attrs="{'invisible':['|', ('type', '!=', 'pay_later'), '&', ('type', '=', 'pay_later'), ('split_transactions', '=', True)]}" role="alert">
|
||||
Only cash and bank journals are allowed in the <strong>Journal</strong> field.
|
||||
</div>
|
||||
<group name="Payment methods">
|
||||
<field name="hide_use_payment_terminal" invisible="1"/>
|
||||
<group>
|
||||
<field name="hide_use_payment_terminal" invisible="1"/>
|
||||
<field name="receivable_account_id" groups="account.group_account_readonly" />
|
||||
<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="split_transactions"/>
|
||||
<field name="journal_id" attrs="{'required': [('split_transactions', '=', False)]}" placeholder="Leave empty to use the receivable account of customer" />
|
||||
<field name="outstanding_account_id" groups="account.group_account_readonly" attrs="{'invisible': [('type', '!=', 'bank')]}" placeholder="Leave empty to use the default account from the company setting" />
|
||||
<field name="receivable_account_id" groups="account.group_account_readonly" attrs="{'invisible': [('split_transactions', '=', True)]}" placeholder="Leave empty to use the default account from the company setting" />
|
||||
<field name="company_id" readonly="1" groups="base.group_multi_company" />
|
||||
</group>
|
||||
<group>
|
||||
<field name="use_payment_terminal" attrs="{'invisible': [('hide_use_payment_terminal', '=', True)]}" />
|
||||
<group attrs="{'invisible': ['|', ('hide_use_payment_terminal', '=', False), ('type', 'in', ['cash', 'pay_later'])]}">
|
||||
<div>
|
||||
<h2>Tips:</h2>
|
||||
<p>
|
||||
Go to <a href="#" name="%(action_pos_configuration)d" type="action" class="btn-link o_form_uri" role="button">Configurations > Settings</a>
|
||||
<strong> > Payment Terminals</strong>
|
||||
in order to install a Payment Terminal and make a fully integrated payment method.
|
||||
</p>
|
||||
</div>
|
||||
</group>
|
||||
<group attrs="{'invisible': [('hide_use_payment_terminal', '=', True)]}">
|
||||
<field name="use_payment_terminal" />
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
@@ -34,13 +48,13 @@
|
||||
<field name="name">pos.payment.method.tree</field>
|
||||
<field name="model">pos.payment.method</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Payment Methods">
|
||||
<field name="hide_use_payment_terminal" invisible="1"/>
|
||||
<tree string="Payment Methods" create="1" delete="1">
|
||||
<field name="type" invisible="1"/>
|
||||
<field name="name" />
|
||||
<field name="receivable_account_id" groups="account.group_account_readonly"/>
|
||||
<field name="is_cash_count" />
|
||||
<field name="split_transactions" groups="base.group_no_one" />
|
||||
<field name="use_payment_terminal" attrs="{'invisible': [('hide_use_payment_terminal', '=', True)]}" />
|
||||
<field name="split_transactions" optional="hide" />
|
||||
<field name="journal_id" attrs="{'required': [('split_transactions', '=', False)]}" />
|
||||
<field name="outstanding_account_id" groups="account.group_account_readonly" optional="hide" attrs="{'invisible': [('type', '!=', 'bank')]}" />
|
||||
<field name="receivable_account_id" groups="account.group_account_readonly" optional="hide" attrs="{'invisible': [('split_transactions', '=', True)]}" />
|
||||
<field name="company_id" groups="base.group_multi_company" />
|
||||
</tree>
|
||||
</field>
|
||||
|
||||
@@ -51,7 +51,6 @@
|
||||
class="oe_stat_button"
|
||||
icon="fa-bars"
|
||||
string="Journal Items"
|
||||
attrs="{'invisible':[('move_id', '=', False)]}"
|
||||
groups="account.group_account_readonly"
|
||||
>
|
||||
</button>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="point_of_sale.report_invoice_document" inherit_id="account.report_invoice_document">
|
||||
<xpath expr="//i[hasclass('oe_payment_label')]" position="inside">
|
||||
<t t-if="payment_vals.get('pos_payment_name')">
|
||||
using <t t-esc="payment_vals['pos_payment_name']" />
|
||||
</t>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
@@ -8,7 +8,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[hasclass('settings')]" position="inside">
|
||||
<div class="app_settings_block" data-string="Point of sale" string="Point of Sale" data-key="point_of_sale" groups="point_of_sale.group_pos_manager">
|
||||
<h2>Taxes</h2>
|
||||
<h2>Accounting</h2>
|
||||
<div class="row mt16 o_settings_container" name="taxes_setting_container">
|
||||
<div class="col-12 col-lg-6 o_setting_box"
|
||||
id="default_sales_tax_setting"
|
||||
@@ -28,6 +28,21 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 o_setting_box"
|
||||
groups="account.group_account_readonly"
|
||||
title="This account is used as intermediary account when nothing is set in a payment method.">
|
||||
<div class="o_setting_right_pane">
|
||||
<div>
|
||||
<label string="Default Intermediary Account" for="sale_tax_id"/>
|
||||
<div class="text-muted">
|
||||
This account is used as intermediary account when nothing is set in a payment method.
|
||||
</div>
|
||||
<div class="content-group mt16">
|
||||
<field name="account_default_pos_receivable_account_id" colspan="4" nolabel="1" domain="[('reconcile', '=', True), ('user_type_id.type', '=', 'receivable'), ('company_id', '=', company_id)]"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2>Pricing</h2>
|
||||
<div class="row mt16 o_settings_container" name="pricing_setting_container">
|
||||
|
||||
Reference in New Issue
Block a user