[IMP] point_of_sale: show wizard when closing pos.session failed

When closing pos.session and it turned out that during the creation
of journal entry the move lines are not balanced, pos automatically
balances the lines by crediting the amount to balance and recording
it to the default pos receivable account.

In this commit, we are changing the behavior by allowing the user
to choose which account the imbalance amount be recorded. So the
workflow becomes:

1. Close pos session fails.
2. Wizard shows the amount to balance and asks which account to
record it.
3. Confirm the wizard to successfully close the session.

closes odoo/odoo#62425

Signed-off-by: pimodoo <pimodoo@users.noreply.github.com>
This commit is contained in:
Joseph Caburnay
2021-02-15 12:06:31 +00:00
parent 2d2019d4d2
commit 51a08bbfa3
8 changed files with 126 additions and 35 deletions
+10 -1
View File
@@ -16,6 +16,15 @@ import json
import re
import warnings
class ImbalanceJournalEntryError(UserError):
"""Specialized UserError raised by `_check_balanced` method."""
def __init__(self, message, move_ids, imbalance_amounts):
super().__init__(message)
self.move_ids = move_ids
self.imbalance_amounts = imbalance_amounts
#forbidden fields
INTEGRITY_HASH_MOVE_FIELDS = ('date', 'journal_id', 'company_id')
INTEGRITY_HASH_LINE_FIELDS = ('debit', 'credit', 'account_id', 'partner_id')
@@ -1671,7 +1680,7 @@ class AccountMove(models.Model):
if query_res:
ids = [res[0] for res in query_res]
sums = [res[1] for res in query_res]
raise UserError(_("Cannot create unbalanced journal entry. Ids: %s\nDifferences debit - credit: %s") % (ids, sums))
raise ImbalanceJournalEntryError(_("Cannot create unbalanced journal entry. Ids: %s\nDifferences debit - credit: %s") % (ids, sums), ids, sums)
def _check_fiscalyear_lock_date(self):
for move in self:
+1
View File
@@ -17,6 +17,7 @@
'wizard/pos_box.xml',
'wizard/pos_details.xml',
'wizard/pos_payment.xml',
'wizard/pos_close_session_wizard.xml',
'views/pos_assets_common.xml',
'views/pos_assets_index.xml',
'views/pos_assets_qunit.xml',
+65 -33
View File
@@ -8,6 +8,7 @@ from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError
from odoo.tools import float_is_zero, float_compare
from odoo.addons.account.models.account_move import ImbalanceJournalEntryError
class PosSession(models.Model):
_name = 'pos.session'
@@ -256,14 +257,14 @@ class PosSession(models.Model):
session.write(values)
return True
def action_pos_session_closing_control(self):
def action_pos_session_closing_control(self, balancing_account=False, amount_to_balance=0):
self._check_pos_session_balance()
for session in self:
if session.state == 'closed':
raise UserError(_('This session is already closed.'))
session.write({'state': 'closing_control', 'stop_at': fields.Datetime.now()})
if not session.config_id.cash_control:
session.action_pos_session_close()
return session.action_pos_session_close(balancing_account, amount_to_balance)
def _check_pos_session_balance(self):
for session in self:
@@ -275,12 +276,12 @@ class PosSession(models.Model):
self._check_pos_session_balance()
return self.action_pos_session_close()
def action_pos_session_close(self):
def action_pos_session_close(self, balancing_account=False, amount_to_balance=0):
# Session without cash payment method will not have a cash register.
# However, there could be other payment methods, thus, session still
# needs to be validated.
if not self.cash_register_id:
return self._validate_session()
return self._validate_session(balancing_account, amount_to_balance)
if self.cash_control and abs(self.cash_register_difference) > self.config_id.amount_authorized_diff:
# Only pos manager can close statements with cash_register_difference greater than amount_authorized_diff.
@@ -292,9 +293,9 @@ class PosSession(models.Model):
else:
return self._warning_balance_closing()
else:
return self._validate_session()
return self._validate_session(balancing_account, amount_to_balance)
def _validate_session(self):
def _validate_session(self, balancing_account=False, amount_to_balance=0):
self.ensure_one()
if self.order_ids:
self.cash_real_transaction = self.cash_register_total_entry_encoding
@@ -308,19 +309,37 @@ class PosSession(models.Model):
# Users without any accounting rights won't be able to create the journal entry. If this
# case, switch to sudo for creation and posting.
sudo = False
if (
not self.env['account.move'].check_access_rights('create', raise_exception=False)
and self.user_has_groups('point_of_sale.group_pos_user')
):
sudo = True
self.sudo().with_company(self.company_id)._create_account_move()
else:
self.with_company(self.company_id)._create_account_move()
if self.move_id.line_ids:
# 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()
try:
if (
not self.env['account.move'].check_access_rights('create', raise_exception=False)
and self.user_has_groups('point_of_sale.group_pos_user')
):
sudo = True
self.sudo().with_company(self.company_id)._create_account_move(balancing_account, amount_to_balance)
else:
self.with_company(self.company_id)._create_account_move(balancing_account, amount_to_balance)
if self.move_id.line_ids:
# 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()
except ImbalanceJournalEntryError as error:
# Creating the account move is just part of a big database transaction
# when closing a session. There are other database changes that will happen
# before attempting to create the account move, such as, creating the picking
# records.
# We don't, however, want them to be committed when the account move creation
# failed; therefore, we need to roll back this transaction before showing the
# close session wizard.
self.env.cr.rollback()
# When closing a session, single account move is checked which resulted to
# `ImbalanceJournalEntryError`. Thus, there should only be one value in imbalance_amounts
# which is what we need.
if len(error.imbalance_amounts) != 1:
raise UserError(_("Single imbalance amount that matches the pos session's account move is expected."))
return self._close_session_action(error.imbalance_amounts[0])
self.write({'state': 'closed'})
return {
'type': 'ir.actions.client',
@@ -329,6 +348,25 @@ class PosSession(models.Model):
'params': {'menu_id': self.env.ref('point_of_sale.menu_point_root').id},
}
def _close_session_action(self, amount_to_balance):
default_account = self._get_balancing_account()
wizard = self.env['pos.close.session.wizard'].create({
'amount_to_balance': amount_to_balance,
'account_id': default_account.id,
'account_readonly': not self.env.user.has_group('account.group_account_readonly'),
'message': _("There is a difference between the amounts to post and the amounts of the orders, it is probably caused by taxes or accounting configurations changes.")
})
return {
'name': _("Force Close Session"),
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'pos.close.session.wizard',
'res_id': wizard.id,
'target': 'new',
'context': {**self.env.context, 'active_ids': self.ids, 'active_model': 'pos.session'},
}
def _create_picking_at_end_of_session(self):
self.ensure_one()
lines_grouped_by_dest_location = {}
@@ -352,24 +390,17 @@ class PosSession(models.Model):
pickings = self.env['stock.picking']._create_picking_from_pos_order_lines(location_dest_id, lines, picking_type)
pickings.write({'pos_session_id': self.id, 'origin': self.name})
def _create_balancing_line(self, data):
imbalance_amount = 0
for line in self.move_id.line_ids:
# it is an excess debit so it should be credited
imbalance_amount += line.debit - line.credit
if (not float_is_zero(imbalance_amount, precision_rounding=self.currency_id.rounding)):
balancing_vals = self._prepare_balancing_line_vals(imbalance_amount, self.move_id)
def _create_balancing_line(self, data, balancing_account, amount_to_balance):
if (not float_is_zero(amount_to_balance, precision_rounding=self.currency_id.rounding)):
balancing_vals = self._prepare_balancing_line_vals(amount_to_balance, self.move_id, balancing_account)
MoveLine = data.get('MoveLine')
MoveLine.create(balancing_vals)
return data
def _prepare_balancing_line_vals(self, imbalance_amount, move):
account = self._get_balancing_account()
def _prepare_balancing_line_vals(self, imbalance_amount, move, balancing_account):
partial_vals = {
'name': _('Difference at closing PoS session'),
'account_id': account.id,
'account_id': balancing_account.id,
'move_id': move.id,
'partner_id': False,
}
@@ -384,7 +415,7 @@ class PosSession(models.Model):
def _get_balancing_account(self):
return self.company_id.account_default_pos_receivable_account_id or self.env['ir.property'].get('property_account_receivable_id', 'res.partner')
def _create_account_move(self):
def _create_account_move(self, balancing_account=False, amount_to_balance=0):
""" Create account.move and account.move.line records for this session.
Side-effects include:
@@ -408,7 +439,8 @@ class PosSession(models.Model):
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)
data = self._create_balancing_line(data)
if balancing_account and amount_to_balance:
data = self._create_balancing_line(data, balancing_account, amount_to_balance)
if account_move.line_ids:
account_move._post()
@@ -50,3 +50,4 @@ access_closing_balance_confirm_wizard,access.closing.balance.confirm.wizard,mode
access_pos_details_wizard,access.pos.details.wizard,model_pos_details_wizard,point_of_sale.group_pos_manager,1,1,1,0
access_pos_make_payment,access.pos.make.payment,model_pos_make_payment,point_of_sale.group_pos_manager,1,1,1,0
access_money_in_out_wizard,access.money.in.out.wizard,model_cash_box_out,point_of_sale.group_pos_user,1,1,1,0
access_pos_close_session_wizard,access.pos.close.session.wizard,model_pos_close_session_wizard,point_of_sale.group_pos_user,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
50 access_pos_details_wizard access.pos.details.wizard model_pos_details_wizard point_of_sale.group_pos_manager 1 1 1 0
51 access_pos_make_payment access.pos.make.payment model_pos_make_payment point_of_sale.group_pos_manager 1 1 1 0
52 access_money_in_out_wizard access.money.in.out.wizard model_cash_box_out point_of_sale.group_pos_user 1 1 1 0
53 access_pos_close_session_wizard access.pos.close.session.wizard model_pos_close_session_wizard point_of_sale.group_pos_user 1 1 1 0
@@ -913,7 +913,13 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
dummy_50_perc_tax.unlink()
# close session (should not fail here)
pos_session.action_pos_session_closing_control()
# We don't call `action_pos_session_closing_control` to force the failed
# closing which will return the action because the internal rollback call messes
# with the rollback of the test runner. So instead, we directly call the method
# that returns the action by specifying the imbalance amount.
action = pos_session._close_session_action(5.0)
wizard = self.env['pos.close.session.wizard'].browse(action['res_id'])
wizard.with_context(action['context']).close_session()
# check the difference line
diff_line = pos_session.move_id.line_ids.filtered(lambda line: line.name == 'Difference at closing PoS session')
+1
View File
@@ -4,3 +4,4 @@
from . import pos_box
from . import pos_details
from . import pos_payment
from . import pos_close_session_wizard
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class PosCloseSessionWizard(models.TransientModel):
_name = "pos.close.session.wizard"
_description = "Close Session Wizard"
amount_to_balance = fields.Float("Amount to balance")
account_id = fields.Many2one("account.account", "Destination account")
account_readonly = fields.Boolean("Destination account is readonly")
message = fields.Text("Information message")
def close_session(self):
session = self.env["pos.session"].browse(self.env.context["active_ids"])
return session.action_pos_session_closing_control(
self.account_id, self.amount_to_balance
)
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_form_pos_close_session_wizard" model="ir.ui.view">
<field name="name">pos.close.session.wizard.form</field>
<field name="model">pos.close.session.wizard</field>
<field name="arch" type="xml">
<form string="Force Close Session">
<p><field name="message" readonly="1" /></p>
<group>
<field name="account_readonly" invisible="1" />
<field name="amount_to_balance" readonly="1" />
<field name="account_id" attrs="{'readonly': [('account_readonly', '==', True)]}"/>
</group>
<footer>
<button name="close_session" string="Close Session" type="object" class="btn-primary" />
<button special="cancel" string="Cancel" class="btn-secondary" />
</footer>
</form>
</field>
</record>
</odoo>