diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index b8a6d5f6c8a..d038cfab430 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -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:
diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py
index 79048dc8176..5f533501a31 100644
--- a/addons/account/models/account_payment.py
+++ b/addons/account/models/account_payment.py
@@ -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:
diff --git a/addons/point_of_sale/__manifest__.py b/addons/point_of_sale/__manifest__.py
index 197e200d820..ec1659b0041 100644
--- a/addons/point_of_sale/__manifest__.py
+++ b/addons/point_of_sale/__manifest__.py
@@ -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',
diff --git a/addons/point_of_sale/models/__init__.py b/addons/point_of_sale/models/__init__.py
index f6ec809e5ce..83aaa1cdc15 100644
--- a/addons/point_of_sale/models/__init__.py
+++ b/addons/point_of_sale/models/__init__.py
@@ -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
diff --git a/addons/point_of_sale/models/account_journal.py b/addons/point_of_sale/models/account_journal.py
index 01371496185..3e41f4476b8 100644
--- a/addons/point_of_sale/models/account_journal.py
+++ b/addons/point_of_sale/models/account_journal.py
@@ -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')
diff --git a/addons/point_of_sale/models/account_move.py b/addons/point_of_sale/models/account_move.py
index e662a21f60a..3768ce9ba08 100644
--- a/addons/point_of_sale/models/account_move.py
+++ b/addons/point_of_sale/models/account_move.py
@@ -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'
diff --git a/addons/point_of_sale/models/account_payment.py b/addons/point_of_sale/models/account_payment.py
new file mode 100644
index 00000000000..51bfee038dd
--- /dev/null
+++ b/addons/point_of_sale/models/account_payment.py
@@ -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
diff --git a/addons/point_of_sale/models/pos_config.py b/addons/point_of_sale/models/pos_config.py
index 7f2a55ee76e..34fe57c8096 100644
--- a/addons/point_of_sale/models/pos_config.py
+++ b/addons/point_of_sale/models/pos_config.py
@@ -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,
diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py
index 565a6f0560a..2e2ec4714a7 100644
--- a/addons/point_of_sale/models/pos_order.py
+++ b/addons/point_of_sale/models/pos_order.py
@@ -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,
diff --git a/addons/point_of_sale/models/pos_payment.py b/addons/point_of_sale/models/pos_payment.py
index 4b571f009a0..5470c00e8ad 100644
--- a/addons/point_of_sale/models/pos_payment.py
+++ b/addons/point_of_sale/models/pos_payment.py
@@ -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
diff --git a/addons/point_of_sale/models/pos_payment_method.py b/addons/point_of_sale/models/pos_payment_method.py
index 42891c6a590..a7d8fd0452a 100644
--- a/addons/point_of_sale/models/pos_payment_method.py
+++ b/addons/point_of_sale/models/pos_payment_method.py
@@ -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)
diff --git a/addons/point_of_sale/models/pos_session.py b/addons/point_of_sale/models/pos_session.py
index 316fa5a864a..3c7f15c90b6 100644
--- a/addons/point_of_sale/models/pos_session.py
+++ b/addons/point_of_sale/models/pos_session.py
@@ -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 {
diff --git a/addons/point_of_sale/models/res_config_settings.py b/addons/point_of_sale/models/res_config_settings.py
index c2cb589e624..c3cfc256d99 100644
--- a/addons/point_of_sale/models/res_config_settings.py
+++ b/addons/point_of_sale/models/res_config_settings.py
@@ -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()
diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css
index 25e4b37059a..7d17250dff5 100644
--- a/addons/point_of_sale/static/src/css/pos.css
+++ b/addons/point_of_sale/static/src/css/pos.css
@@ -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;
diff --git a/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/ControlButtons/InvoiceButton.js b/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/ControlButtons/InvoiceButton.js
index 53b858ba492..f8e6dd6564c 100644
--- a/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/ControlButtons/InvoiceButton.js
+++ b/addons/point_of_sale/static/src/js/Screens/OrderManagementScreen/ControlButtons/InvoiceButton.js
@@ -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;
diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js
index 49a9a612999..1216e3b8032 100644
--- a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js
+++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js
@@ -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'),
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index a4d41f3055b..f937d23ce1a 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -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();
diff --git a/addons/point_of_sale/views/point_of_sale_report.xml b/addons/point_of_sale/views/point_of_sale_report.xml
index 0f2fabb548e..120557b56f2 100644
--- a/addons/point_of_sale/views/point_of_sale_report.xml
+++ b/addons/point_of_sale/views/point_of_sale_report.xml
@@ -10,15 +10,6 @@
+ Go to Configurations > Settings + > Payment Terminals + in order to install a Payment Terminal and make a fully integrated payment method. +
+