|
|
|
@@ -4,7 +4,7 @@
|
|
|
|
|
from collections import defaultdict
|
|
|
|
|
from datetime import timedelta
|
|
|
|
|
|
|
|
|
|
from odoo import api, fields, models, _
|
|
|
|
|
from odoo import api, fields, models, _, Command
|
|
|
|
|
from odoo.exceptions import AccessError, UserError, ValidationError
|
|
|
|
|
from odoo.tools import float_is_zero, float_compare
|
|
|
|
|
|
|
|
|
@@ -268,7 +268,8 @@ class PosSession(models.Model):
|
|
|
|
|
session.write(values)
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def action_pos_session_closing_control(self, balancing_account=False, amount_to_balance=0):
|
|
|
|
|
def action_pos_session_closing_control(self, balancing_account=False, amount_to_balance=0, bank_payment_method_diffs=None):
|
|
|
|
|
bank_payment_method_diffs = bank_payment_method_diffs or {}
|
|
|
|
|
self._check_pos_session_balance()
|
|
|
|
|
for session in self:
|
|
|
|
|
if any(order.state == 'draft' for order in session.order_ids):
|
|
|
|
@@ -277,7 +278,7 @@ class PosSession(models.Model):
|
|
|
|
|
raise UserError(_('This session is already closed.'))
|
|
|
|
|
session.write({'state': 'closing_control', 'stop_at': fields.Datetime.now()})
|
|
|
|
|
if not session.config_id.cash_control:
|
|
|
|
|
return session.action_pos_session_close(balancing_account, amount_to_balance)
|
|
|
|
|
return session.action_pos_session_close(balancing_account, amount_to_balance, bank_payment_method_diffs)
|
|
|
|
|
# If the session is in rescue, we only compute the payments in the cash register
|
|
|
|
|
# It is not yet possible to close a rescue session through the front end, see `close_session_from_ui`
|
|
|
|
|
if session.rescue and session.config_id.cash_control:
|
|
|
|
@@ -289,7 +290,7 @@ class PosSession(models.Model):
|
|
|
|
|
|
|
|
|
|
session.cash_register_id.balance_end_real = total_cash
|
|
|
|
|
|
|
|
|
|
return session.action_pos_session_validate(balancing_account, amount_to_balance)
|
|
|
|
|
return session.action_pos_session_validate(balancing_account, amount_to_balance, bank_payment_method_diffs)
|
|
|
|
|
|
|
|
|
|
def _check_pos_session_balance(self):
|
|
|
|
|
for session in self:
|
|
|
|
@@ -297,18 +298,21 @@ class PosSession(models.Model):
|
|
|
|
|
if (statement != session.cash_register_id) and (statement.balance_end != statement.balance_end_real):
|
|
|
|
|
statement.write({'balance_end_real': statement.balance_end})
|
|
|
|
|
|
|
|
|
|
def action_pos_session_validate(self, balancing_account=False, amount_to_balance=0):
|
|
|
|
|
def action_pos_session_validate(self, balancing_account=False, amount_to_balance=0, bank_payment_method_diffs=None):
|
|
|
|
|
bank_payment_method_diffs = bank_payment_method_diffs or {}
|
|
|
|
|
self._check_pos_session_balance()
|
|
|
|
|
return self.action_pos_session_close(balancing_account, amount_to_balance)
|
|
|
|
|
return self.action_pos_session_close(balancing_account, amount_to_balance, bank_payment_method_diffs)
|
|
|
|
|
|
|
|
|
|
def action_pos_session_close(self, balancing_account=False, amount_to_balance=0):
|
|
|
|
|
def action_pos_session_close(self, balancing_account=False, amount_to_balance=0, bank_payment_method_diffs=None):
|
|
|
|
|
bank_payment_method_diffs = bank_payment_method_diffs or {}
|
|
|
|
|
# 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.
|
|
|
|
|
self._check_bank_statement_state()
|
|
|
|
|
return self._validate_session(balancing_account, amount_to_balance)
|
|
|
|
|
return self._validate_session(balancing_account, amount_to_balance, bank_payment_method_diffs)
|
|
|
|
|
|
|
|
|
|
def _validate_session(self, balancing_account=False, amount_to_balance=0):
|
|
|
|
|
def _validate_session(self, balancing_account=False, amount_to_balance=0, bank_payment_method_diffs=None):
|
|
|
|
|
bank_payment_method_diffs = bank_payment_method_diffs or {}
|
|
|
|
|
self.ensure_one()
|
|
|
|
|
sudo = self.user_has_groups('point_of_sale.group_pos_user')
|
|
|
|
|
if self.order_ids or self.statement_ids.line_ids:
|
|
|
|
@@ -322,10 +326,10 @@ class PosSession(models.Model):
|
|
|
|
|
self._create_picking_at_end_of_session()
|
|
|
|
|
self.order_ids.filtered(lambda o: not o.is_total_cost_computed)._compute_total_cost_at_session_closing(self.picking_ids.move_lines)
|
|
|
|
|
try:
|
|
|
|
|
data = self.with_company(self.company_id)._create_account_move(balancing_account, amount_to_balance)
|
|
|
|
|
data = self.with_company(self.company_id)._create_account_move(balancing_account, amount_to_balance, bank_payment_method_diffs)
|
|
|
|
|
except AccessError as e:
|
|
|
|
|
if sudo:
|
|
|
|
|
data = self.sudo().with_company(self.company_id)._create_account_move(balancing_account, amount_to_balance)
|
|
|
|
|
data = self.sudo().with_company(self.company_id)._create_account_move(balancing_account, amount_to_balance, bank_payment_method_diffs)
|
|
|
|
|
else:
|
|
|
|
|
raise e
|
|
|
|
|
|
|
|
|
@@ -360,6 +364,7 @@ class PosSession(models.Model):
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def _close_session_action(self, amount_to_balance):
|
|
|
|
|
# NOTE This can't handle `bank_payment_method_diffs` because there is no field in the wizard that can carry it.
|
|
|
|
|
default_account = self._get_balancing_account()
|
|
|
|
|
wizard = self.env['pos.close.session.wizard'].create({
|
|
|
|
|
'amount_to_balance': amount_to_balance,
|
|
|
|
@@ -378,18 +383,23 @@ class PosSession(models.Model):
|
|
|
|
|
'context': {**self.env.context, 'active_ids': self.ids, 'active_model': 'pos.session'},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def close_session_from_ui(self):
|
|
|
|
|
def close_session_from_ui(self, bank_payment_method_diff_pairs=None):
|
|
|
|
|
"""Calling this method will try to close the session.
|
|
|
|
|
|
|
|
|
|
param bank_payment_method_diff_pairs: list[(int, float)]
|
|
|
|
|
Pairs of payment_method_id and diff_amount which will be used to post
|
|
|
|
|
loss/profit when closing the session.
|
|
|
|
|
|
|
|
|
|
If successful, it returns {'successful': True}
|
|
|
|
|
Otherwise, it returns {'successful': False, 'message': str, 'redirect': bool}.
|
|
|
|
|
'redirect' is a boolean used to know whether we redirect the user to the back end or not.
|
|
|
|
|
When necessary, error (i.e. UserError, AccessError) is raised which should redirect the user to the back end.
|
|
|
|
|
"""
|
|
|
|
|
bank_payment_method_diffs = dict(bank_payment_method_diff_pairs or [])
|
|
|
|
|
self.ensure_one()
|
|
|
|
|
# Even if this is called in `post_closing_cash_details`, we need to call this here too for case
|
|
|
|
|
# where cash_control = False
|
|
|
|
|
check_closing_session = self._cannot_close_session()
|
|
|
|
|
check_closing_session = self._cannot_close_session(bank_payment_method_diffs)
|
|
|
|
|
if check_closing_session:
|
|
|
|
|
return check_closing_session
|
|
|
|
|
|
|
|
|
@@ -399,7 +409,7 @@ class PosSession(models.Model):
|
|
|
|
|
# validate_result = self._validate_session()
|
|
|
|
|
# because some functions are being used and overridden in other modules...
|
|
|
|
|
# so we'll try to use the original flow as of now for the moment
|
|
|
|
|
validate_result = self.action_pos_session_closing_control()
|
|
|
|
|
validate_result = self.action_pos_session_closing_control(bank_payment_method_diffs=bank_payment_method_diffs)
|
|
|
|
|
|
|
|
|
|
# If an error is raised, the user will still be redirected to the back end to manually close the session.
|
|
|
|
|
# If the return result is a dict, this means that normally we have a redirection or a wizard => we redirect the user
|
|
|
|
@@ -443,16 +453,72 @@ class PosSession(models.Model):
|
|
|
|
|
|
|
|
|
|
return {'successful': True}
|
|
|
|
|
|
|
|
|
|
def _cannot_close_session(self):
|
|
|
|
|
def _create_diff_account_move_for_split_payment_method(self, payment_method, diff_amount):
|
|
|
|
|
self.ensure_one()
|
|
|
|
|
|
|
|
|
|
get_diff_vals_result = self._get_diff_vals(payment_method.id, diff_amount)
|
|
|
|
|
if not get_diff_vals_result:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
source_vals, dest_vals = get_diff_vals_result
|
|
|
|
|
diff_move = self.env['account.move'].create({
|
|
|
|
|
'journal_id': payment_method.journal_id.id,
|
|
|
|
|
'date': fields.Date.context_today(self),
|
|
|
|
|
'ref': self._get_diff_account_move_ref(payment_method),
|
|
|
|
|
'line_ids': [Command.create(source_vals), Command.create(dest_vals)]
|
|
|
|
|
})
|
|
|
|
|
diff_move._post()
|
|
|
|
|
|
|
|
|
|
def _get_diff_account_move_ref(self, payment_method):
|
|
|
|
|
return _('Closing difference in %s (%s)', payment_method.name, self.name)
|
|
|
|
|
|
|
|
|
|
def _get_diff_vals(self, payment_method_id, diff_amount):
|
|
|
|
|
payment_method = self.env['pos.payment.method'].browse(payment_method_id)
|
|
|
|
|
diff_compare_to_zero = self.currency_id.compare_amounts(diff_amount, 0)
|
|
|
|
|
source_account = payment_method.outstanding_account_id or self.company_id.account_journal_payment_debit_account_id
|
|
|
|
|
destination_account = self.env['account.account']
|
|
|
|
|
|
|
|
|
|
if (diff_compare_to_zero > 0):
|
|
|
|
|
destination_account = payment_method.journal_id.profit_account_id
|
|
|
|
|
elif (diff_compare_to_zero < 0):
|
|
|
|
|
destination_account = payment_method.journal_id.loss_account_id
|
|
|
|
|
|
|
|
|
|
if (diff_compare_to_zero == 0 or not source_account):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
amounts = self._update_amounts({'amount': 0, 'amount_converted': 0}, {'amount': diff_amount}, self.stop_at)
|
|
|
|
|
source_vals = self._debit_amounts({'account_id': source_account.id}, amounts['amount'], amounts['amount_converted'])
|
|
|
|
|
dest_vals = self._credit_amounts({'account_id': destination_account.id}, amounts['amount'], amounts['amount_converted'])
|
|
|
|
|
return [source_vals, dest_vals]
|
|
|
|
|
|
|
|
|
|
def _cannot_close_session(self, bank_payment_method_diffs=None):
|
|
|
|
|
"""
|
|
|
|
|
Add check in this method if you want to return or raise an error when trying to either post cash details
|
|
|
|
|
or close the session. Raising an error will always redirect the user to the back end.
|
|
|
|
|
It should return {'successful': False, 'message': str, 'redirect': bool} if we can't close the session
|
|
|
|
|
"""
|
|
|
|
|
bank_payment_method_diffs = bank_payment_method_diffs or {}
|
|
|
|
|
if any(order.state == 'draft' for order in self.order_ids):
|
|
|
|
|
return {'successful': False, 'message': _("You cannot close the POS when orders are still in draft"), 'redirect': False}
|
|
|
|
|
if self.state == 'closed':
|
|
|
|
|
return {'successful': False, 'message': _("This session is already closed."), 'redirect': True}
|
|
|
|
|
if bank_payment_method_diffs:
|
|
|
|
|
no_loss_account = self.env['account.journal']
|
|
|
|
|
no_profit_account = self.env['account.journal']
|
|
|
|
|
for payment_method in self.env['pos.payment.method'].browse(bank_payment_method_diffs.keys()):
|
|
|
|
|
journal = payment_method.journal_id
|
|
|
|
|
compare_to_zero = self.currency_id.compare_amounts(bank_payment_method_diffs.get(payment_method.id), 0)
|
|
|
|
|
if compare_to_zero == -1 and not journal.loss_account_id:
|
|
|
|
|
no_loss_account |= journal
|
|
|
|
|
elif compare_to_zero == 1 and not journal.profit_account_id:
|
|
|
|
|
no_profit_account |= journal
|
|
|
|
|
message = ''
|
|
|
|
|
if no_loss_account:
|
|
|
|
|
message += _("Need loss account for the following journals to post the lost amount: %s\n", ', '.join(no_loss_account.mapped('name')))
|
|
|
|
|
if no_profit_account:
|
|
|
|
|
message += _("Need profit account for the following journals to post the gained amount: %s", ', '.join(no_profit_account.mapped('name')))
|
|
|
|
|
if message:
|
|
|
|
|
return {'successful': False, 'message': message, 'redirect': False}
|
|
|
|
|
|
|
|
|
|
def get_closing_control_data(self):
|
|
|
|
|
self.ensure_one()
|
|
|
|
@@ -498,6 +564,7 @@ class PosSession(models.Model):
|
|
|
|
|
'other_payment_methods': [{
|
|
|
|
|
'name': pm.name,
|
|
|
|
|
'amount': sum(orders.payment_ids.filtered(lambda p: p.payment_method_id == pm).mapped('amount')),
|
|
|
|
|
'number': len(orders.payment_ids.filtered(lambda p: p.payment_method_id == pm)),
|
|
|
|
|
'id': pm.id,
|
|
|
|
|
'type': pm.type,
|
|
|
|
|
} for pm in other_payment_method_ids],
|
|
|
|
@@ -554,7 +621,7 @@ class PosSession(models.Model):
|
|
|
|
|
propoerty_account = self.env['ir.property']._get('property_account_receivable_id', 'res.partner')
|
|
|
|
|
return self.company_id.account_default_pos_receivable_account_id or propoerty_account or self.env['account.account']
|
|
|
|
|
|
|
|
|
|
def _create_account_move(self, balancing_account=False, amount_to_balance=0):
|
|
|
|
|
def _create_account_move(self, balancing_account=False, amount_to_balance=0, bank_payment_method_diffs=None):
|
|
|
|
|
""" Create account.move and account.move.line records for this session.
|
|
|
|
|
|
|
|
|
|
Side-effects include:
|
|
|
|
@@ -572,7 +639,7 @@ class PosSession(models.Model):
|
|
|
|
|
})
|
|
|
|
|
self.write({'move_id': account_move.id})
|
|
|
|
|
|
|
|
|
|
data = {}
|
|
|
|
|
data = {'bank_payment_method_diffs': bank_payment_method_diffs or {}}
|
|
|
|
|
data = self._accumulate_amounts(data)
|
|
|
|
|
data = self._create_non_reconciliable_move_lines(data)
|
|
|
|
|
data = self._create_bank_payment_moves(data)
|
|
|
|
@@ -792,12 +859,13 @@ class PosSession(models.Model):
|
|
|
|
|
def _create_bank_payment_moves(self, data):
|
|
|
|
|
combine_receivables_bank = data.get('combine_receivables_bank')
|
|
|
|
|
split_receivables_bank = data.get('split_receivables_bank')
|
|
|
|
|
bank_payment_method_diffs = data.get('bank_payment_method_diffs')
|
|
|
|
|
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_receivable_line = self._create_combine_account_payment(payment_method, amounts, diff_amount=bank_payment_method_diffs.get(payment_method.id) or 0)
|
|
|
|
|
payment_method_to_receivable_lines[payment_method] = combine_receivable_line | payment_receivable_line
|
|
|
|
|
|
|
|
|
|
for payment, amounts in split_receivables_bank.items():
|
|
|
|
@@ -805,6 +873,9 @@ class PosSession(models.Model):
|
|
|
|
|
payment_receivable_line = self._create_split_account_payment(payment, amounts)
|
|
|
|
|
payment_to_receivable_lines[payment] = split_receivable_line | payment_receivable_line
|
|
|
|
|
|
|
|
|
|
for bank_payment_method in self.payment_method_ids.filtered(lambda pm: pm.type == 'bank' and pm.split_transactions):
|
|
|
|
|
self._create_diff_account_move_for_split_payment_method(bank_payment_method, bank_payment_method_diffs.get(bank_payment_method.id) or 0)
|
|
|
|
|
|
|
|
|
|
data['payment_method_to_receivable_lines'] = payment_method_to_receivable_lines
|
|
|
|
|
data['payment_to_receivable_lines'] = payment_to_receivable_lines
|
|
|
|
|
return data
|
|
|
|
@@ -821,7 +892,7 @@ class PosSession(models.Model):
|
|
|
|
|
MoveLine.create(vals)
|
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
def _create_combine_account_payment(self, payment_method, amounts):
|
|
|
|
|
def _create_combine_account_payment(self, payment_method, amounts, diff_amount):
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
@@ -838,9 +909,29 @@ class PosSession(models.Model):
|
|
|
|
|
'pos_payment_method_id': payment_method.id,
|
|
|
|
|
'pos_session_id': self.id,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
diff_amount_compare_to_zero = self.currency_id.compare_amounts(diff_amount, 0)
|
|
|
|
|
if diff_amount_compare_to_zero != 0:
|
|
|
|
|
self._apply_diff_on_account_payment_move(account_payment, payment_method, diff_amount)
|
|
|
|
|
|
|
|
|
|
account_payment.action_post()
|
|
|
|
|
return account_payment.move_id.line_ids.filtered(lambda line: line.account_id == account_payment.destination_account_id)
|
|
|
|
|
|
|
|
|
|
def _apply_diff_on_account_payment_move(self, account_payment, payment_method, diff_amount):
|
|
|
|
|
source_vals, dest_vals = self._get_diff_vals(payment_method.id, diff_amount)
|
|
|
|
|
outstanding_line = account_payment.move_id.line_ids.filtered(lambda line: line.account_id.id == source_vals['account_id'])
|
|
|
|
|
new_balance = outstanding_line.balance + diff_amount
|
|
|
|
|
new_balance_compare_to_zero = self.currency_id.compare_amounts(new_balance, 0)
|
|
|
|
|
account_payment.move_id.write({
|
|
|
|
|
'line_ids': [
|
|
|
|
|
Command.create(dest_vals),
|
|
|
|
|
Command.update(outstanding_line.id, {
|
|
|
|
|
'debit': new_balance_compare_to_zero > 0 and new_balance or 0.0,
|
|
|
|
|
'credit': new_balance_compare_to_zero < 0 and -new_balance or 0.0
|
|
|
|
|
})
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
def _create_split_account_payment(self, payment, amounts):
|
|
|
|
|
payment_method = payment.payment_method_id
|
|
|
|
|
if not payment_method.journal_id:
|
|
|
|
@@ -1318,6 +1409,17 @@ class PosSession(models.Model):
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def _get_other_related_moves(self):
|
|
|
|
|
# TODO This is not an ideal way to get the diff account.move's for
|
|
|
|
|
# the session. It would be better if there is a relation field where
|
|
|
|
|
# these moves are saved.
|
|
|
|
|
|
|
|
|
|
# Unfortunately, the 'ref' of account.move is not indexed, so
|
|
|
|
|
# we are querying over the account.move.line because its 'ref' is indexed.
|
|
|
|
|
# And yes, we are only concern for split bank payment methods.
|
|
|
|
|
diff_lines_ref = [self._get_diff_account_move_ref(pm) for pm in self.payment_method_ids if pm.type == 'bank' and pm.split_transactions]
|
|
|
|
|
return self.env['account.move.line'].search([('ref', 'in', diff_lines_ref)]).mapped('move_id')
|
|
|
|
|
|
|
|
|
|
def _get_related_account_moves(self):
|
|
|
|
|
pickings = self.picking_ids | self.order_ids.mapped('picking_ids')
|
|
|
|
|
invoices = self.mapped('order_ids.account_move')
|
|
|
|
@@ -1325,7 +1427,8 @@ class PosSession(models.Model):
|
|
|
|
|
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
|
|
|
|
|
other_related_moves = self._get_other_related_moves()
|
|
|
|
|
return invoices | invoice_payments | self.move_id | stock_account_moves | cash_moves | bank_payment_moves | other_related_moves
|
|
|
|
|
|
|
|
|
|
def _get_receivable_account(self, payment_method):
|
|
|
|
|
"""Returns the default pos receivable account if no receivable_account_id is set on the payment method."""
|
|
|
|
|