[IMP] point_of_sale: Implement cash control independent of accounting

Since bank statements are now optional in accounting, cash control logic is moved to `pos.session`.

Part-of: odoo/odoo#99092
This commit is contained in:
PoMa
2022-09-20 15:24:23 +02:00
committed by Laurent Smet
parent 1a8772769e
commit 7e23eda02f
20 changed files with 218 additions and 344 deletions
-1
View File
@@ -13,7 +13,6 @@
'security/ir.model.access.csv',
'data/default_barcode_patterns.xml',
'data/digest_data.xml',
'wizard/pos_box.xml',
'wizard/pos_details.xml',
'wizard/pos_payment.xml',
'wizard/pos_close_session_wizard.xml',
@@ -5,21 +5,22 @@ from odoo import fields, models, api, _
from odoo.exceptions import UserError
class AccountBankStatement(models.Model):
_inherit = 'account.bank.statement'
class AccountBankStatementLine(models.Model):
_inherit = 'account.bank.statement.line'
pos_session_id = fields.Many2one('pos.session', string="Session", copy=False)
account_id = fields.Many2one('account.account', related='journal_id.default_account_id', readonly=True)
# TODO:PoMa check if they need to be converted
# account_id = fields.Many2one('account.account', related='journal_id.default_account_id', readonly=True)
def button_validate_or_action(self):
# OVERRIDE to check the consistency of the statement's state regarding the session's state.
for statement in self:
if statement.pos_session_id.state in ('opened', 'closing_control') and statement.state == 'open':
raise UserError(_("You can't validate a bank statement that is used in an opened Session of a Point of Sale."))
return super(AccountBankStatement, self).button_validate_or_action()
# def button_validate_or_action(self):
# # OVERRIDE to check the consistency of the statement's state regarding the session's state.
# for statement in self:
# if statement.pos_session_id.state in ('opened', 'closing_control') and statement.state == 'open':
# raise UserError(_("You can't validate a bank statement that is used in an opened Session of a Point of Sale."))
# return super(AccountBankStatement, self).button_validate_or_action()
@api.ondelete(at_uninstall=False)
def _unlink_except_linked_to_pos_session(self):
for bs in self:
if bs.pos_session_id:
raise UserError(_("You cannot delete a bank statement linked to Point of Sale session."))
for bsl in self:
if bsl.pos_session_id:
raise UserError(_("You cannot delete a bank statement line linked to Point of Sale session."))
+2 -5
View File
@@ -219,15 +219,12 @@ class PosConfig(models.Model):
for pos_config in self:
session = PosSession.search_read(
[('config_id', '=', pos_config.id), ('state', '=', 'closed')],
['cash_register_balance_end_real', 'stop_at', 'cash_register_id'],
['cash_register_balance_end_real', 'stop_at', 'cash_journal_id'],
order="stop_at desc", limit=1)
if session:
timezone = pytz.timezone(self._context.get('tz') or self.env.user.tz or 'UTC')
pos_config.last_session_closing_date = session[0]['stop_at'].astimezone(timezone).date()
if session[0]['cash_register_id']:
pos_config.last_session_closing_cash = session[0]['cash_register_balance_end_real']
else:
pos_config.last_session_closing_cash = 0
pos_config.last_session_closing_cash = session[0]['cash_register_balance_end_real']
else:
pos_config.last_session_closing_cash = 0
pos_config.last_session_closing_date = False
+2 -2
View File
@@ -170,7 +170,7 @@ class PosOrder(models.Model):
order_bank_statement_lines= self.env['pos.payment'].search([('pos_order_id', '=', order.id)])
order_bank_statement_lines.unlink()
for payments in pos_order['statement_ids']:
for payments in pos_order['statement_ids']: # this is not related to session.statement_ids it is just a variable with a weird name holding payments
order.add_payment(self._payment_fields(order, payments[2]))
order.amount_paid = sum(order.payment_ids.mapped('amount'))
@@ -1011,7 +1011,7 @@ class PosOrder(models.Model):
timezone = pytz.timezone(self._context.get('tz') or self.env.user.tz or 'UTC')
return {
'lines': [[0, 0, line] for line in order.lines.export_for_ui()],
'statement_ids': [[0, 0, payment] for payment in order.payment_ids.export_for_ui()],
'statement_ids': [[0, 0, payment] for payment in order.payment_ids.export_for_ui()], # this is not related to session.statement_ids it is just a variable with a weird name holding payments
'name': order.pos_reference,
'uid': re.search('([0-9-]){14}', order.pos_reference).group(0),
'amount_paid': order.amount_paid,
+148 -143
View File
@@ -11,6 +11,7 @@ from odoo.tools import float_is_zero, float_compare
from odoo.osv.expression import AND, OR
from odoo.service.common import exp_version
class PosSession(models.Model):
_name = 'pos.session'
_order = 'id desc'
@@ -52,16 +53,13 @@ class PosSession(models.Model):
login_number = fields.Integer(string='Login Sequence Number', help='A sequence number that is incremented each time a user resumes the pos session', default=0)
opening_notes = fields.Text(string="Opening Notes")
cash_control = fields.Boolean(compute='_compute_cash_all', string='Has Cash Control', compute_sudo=True)
cash_control = fields.Boolean(compute='_compute_cash_all', string='Has Cash Control', compute_sudo=True, store=True)
cash_journal_id = fields.Many2one('account.journal', compute='_compute_cash_all', string='Cash Journal', store=True)
cash_register_id = fields.Many2one('account.bank.statement', compute='_compute_cash_all', string='Cash Register', store=True)
cash_register_balance_end_real = fields.Monetary(
related='cash_register_id.balance_end_real',
string="Ending Balance",
readonly=True)
cash_register_balance_start = fields.Monetary(
related='cash_register_id.balance_start',
string="Starting Balance",
readonly=True)
cash_register_total_entry_encoding = fields.Monetary(
@@ -78,13 +76,11 @@ class PosSession(models.Model):
string='Before Closing Difference',
help="Difference between the theoretical closing balance and the real closing balance.",
readonly=True)
cash_real_difference = fields.Monetary(string='Difference', readonly=True)
cash_real_transaction = fields.Monetary(string='Transaction', readonly=True)
cash_real_expected = fields.Monetary(string="Expected", readonly=True)
order_ids = fields.One2many('pos.order', 'session_id', string='Orders')
order_count = fields.Integer(compute='_compute_order_count')
statement_ids = fields.One2many('account.bank.statement', 'pos_session_id', string='Cash Statements', readonly=True)
statement_line_ids = fields.One2many('account.bank.statement.line', 'pos_session_id', string='Cash Lines', readonly=True)
failed_pickings = fields.Boolean(compute='_compute_picking_count')
picking_count = fields.Integer(compute='_compute_picking_count')
picking_ids = fields.One2many('stock.picking', 'pos_session_id')
@@ -106,7 +102,7 @@ class PosSession(models.Model):
for session in self:
session.is_in_company_currency = session.currency_id == session.company_id.currency_id
@api.depends('payment_method_ids', 'order_ids', 'cash_register_balance_start', 'cash_register_id')
@api.depends('payment_method_ids', 'order_ids', 'cash_register_balance_start')
def _compute_cash_balance(self):
for session in self:
cash_payment_method = session.payment_method_ids.filtered('is_cash_count')[:1]
@@ -115,7 +111,7 @@ class PosSession(models.Model):
result = self.env['pos.payment']._read_group([('session_id', '=', session.id), ('payment_method_id', '=', cash_payment_method.id)], ['amount'], ['session_id'])
if result:
total_cash_payment = result[0]['amount']
session.cash_register_total_entry_encoding = session.cash_register_id.total_entry_encoding + (
session.cash_register_total_entry_encoding = sum(session.statement_line_ids.mapped('amount')) + (
0.0 if session.state == 'closed' else total_cash_payment
)
session.cash_register_balance_end = session.cash_register_balance_start + session.cash_register_total_entry_encoding
@@ -151,20 +147,16 @@ class PosSession(models.Model):
action['domain'] = [('id', 'in', self.picking_ids.ids)]
return action
@api.depends('config_id', 'statement_ids', 'payment_method_ids')
@api.depends('config_id', 'payment_method_ids')
def _compute_cash_all(self):
# Only one cash register is supported by point_of_sale.
for session in self:
session.cash_journal_id = session.cash_register_id = session.cash_control = False
cash_payment_methods = session.payment_method_ids.filtered('is_cash_count')
if not cash_payment_methods:
session.cash_journal_id = session.cash_control = False
cash_journal = session.payment_method_ids.filtered('is_cash_count')[:1].journal_id
if not cash_journal:
continue
for statement in session.statement_ids:
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
break # stop iteration after finding the cash journal
session.cash_control = session.config_id.cash_control
session.cash_journal_id = cash_journal
@api.constrains('config_id')
def _check_pos_config(self):
@@ -185,10 +177,11 @@ class PosSession(models.Model):
def _check_bank_statement_state(self):
for session in self:
closed_statement_ids = session.statement_ids.filtered(lambda x: x.state != "open")
if closed_statement_ids:
raise UserError(_("Some Cash Registers are already posted. Please reset them to new in order to close the session.\n"
"Cash Registers: %r", list(statement.name for statement in closed_statement_ids)))
posted_stmt_lines = session.statement_line_ids.filtered(lambda x: x.state == "posted")
if posted_stmt_lines:
raise UserError(_("Some Cash Transactions are already posted. "
"Please reset them to new in order to close the session.\n"
"Cash Registers: %r", list(stmt_line.name for stmt_line in posted_stmt_lines)))
def _check_invoices_are_posted(self):
unposted_invoices = self.order_ids.sudo().with_company(self.company_id).account_move.filtered(lambda x: x.state != 'posted')
@@ -216,24 +209,10 @@ class PosSession(models.Model):
if vals.get('name'):
pos_name += ' ' + vals['name']
cash_payment_methods = pos_config.payment_method_ids.filtered(lambda pm: pm.is_cash_count)
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('journal_id'):
ctx['journal_id'] = cash_journal.id if pos_config.cash_control and cash_journal.type == 'cash' else False
st_vals = {
'journal_id': cash_journal.id,
'user_id': self.env.user.id,
'name': pos_name,
}
statement_ids |= statement_ids.with_context(ctx).create(st_vals)
update_stock_at_closing = pos_config.company_id.point_of_sale_update_stock_quantities == "closing"
vals.update({
'name': pos_name,
'statement_ids': [(6, 0, statement_ids.ids)],
'config_id': config_id,
'update_stock_at_closing': update_stock_at_closing,
})
@@ -246,8 +225,7 @@ class PosSession(models.Model):
return sessions
def unlink(self):
for session in self.filtered(lambda s: s.statement_ids):
session.statement_ids.unlink()
self.statement_line_ids.unlink()
return super(PosSession, self).unlink()
def login(self):
@@ -259,7 +237,6 @@ class PosSession(models.Model):
return login_number
def action_pos_session_open(self):
# second browse because we need to refetch the data from the DB for cash_register_id
# we only open sessions that haven't already been opened
for session in self.filtered(lambda session: session.state == 'opening_control'):
values = {}
@@ -267,16 +244,15 @@ class PosSession(models.Model):
values['start_at'] = fields.Datetime.now()
if session.config_id.cash_control and not session.rescue:
last_session = self.search([('config_id', '=', session.config_id.id), ('id', '!=', session.id)], limit=1)
session.cash_register_id.balance_start = last_session.cash_register_id.balance_end_real if last_session else 0
values['state'] = 'opening_control'
session.cash_register_balance_start = last_session.cash_register_balance_end_real # defaults to 0 if lastsession is empty
else:
values['state'] = 'opened'
session.write(values)
if values:
session.write(values)
return True
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):
raise UserError(_("You cannot close the POS when orders are still in draft"))
@@ -290,23 +266,18 @@ class PosSession(models.Model):
if session.rescue and session.config_id.cash_control:
default_cash_payment_method_id = self.payment_method_ids.filtered(lambda pm: pm.type == 'cash')[0]
orders = self.order_ids.filtered(lambda o: o.state == 'paid' or o.state == 'invoiced')
total_cash = sum(
orders.payment_ids.filtered(lambda p: p.payment_method_id == default_cash_payment_method_id).mapped('amount')
) + self.cash_register_balance_start
session.cash_register_id.balance_end_real = total_cash
session.cash_register_balance_end_real = total_cash
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:
for statement in session.statement_ids:
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, 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, bank_payment_method_diffs)
def action_pos_session_close(self, balancing_account=False, amount_to_balance=0, bank_payment_method_diffs=None):
@@ -321,10 +292,8 @@ class PosSession(models.Model):
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:
self.cash_real_transaction = self.cash_register_total_entry_encoding
self.cash_real_expected = self.cash_register_balance_end
self.cash_real_difference = self.cash_register_difference
if self.order_ids or self.statement_line_ids:
self.cash_real_transaction = sum(self.statement_line_ids.mapped('amount'))
if self.state == 'closed':
raise UserError(_('This session is already closed.'))
self._check_if_no_draft_orders()
@@ -363,11 +332,34 @@ class PosSession(models.Model):
self.move_id.sudo().unlink()
self.sudo().with_company(self.company_id)._reconcile_account_move_lines(data)
else:
statement = self.cash_register_id
if not self.config_id.cash_control:
statement.write({'balance_end_real': statement.balance_end})
statement.button_post()
statement.button_validate()
if self.config_id.cash_control:
st_line_vals = {
'journal_id': self.cash_journal_id.id,
'amount': self.cash_register_difference,
'date': self.statement_line_ids.sorted()[-1:].date or fields.Date.context_today(self),
}
if self.cash_register_difference < 0.0:
if not self.cash_journal_id.loss_account_id:
raise UserError(
_('Please go on the %s journal and define a Loss Account. This account will be used to record cash difference.',
self.cash_journal_id.name))
st_line_vals['payment_ref'] = _("Cash difference observed during the counting (Loss)")
st_line_vals['counterpart_account_id'] = self.cash_journal_id.loss_account_id.id
else:
# self.cash_register_difference > 0.0
if not self.cash_journal_id.profit_account_id:
raise UserError(
_('Please go on the %s journal and define a Profit Account. This account will be used to record cash difference.',
self.cash_journal_id.name))
st_line_vals['payment_ref'] = _("Cash difference observed during the counting (Profit)")
st_line_vals['counterpart_account_id'] = self.cash_journal_id.profit_account_id.id
self.env['account.bank.statement.line'].create(st_line_vals)
self.write({'state': 'closed'})
return True
@@ -383,7 +375,6 @@ class PosSession(models.Model):
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,
@@ -453,11 +444,11 @@ class PosSession(models.Model):
if check_closing_session:
return check_closing_session
if not self.cash_register_id:
if not self.cash_journal_id:
# The user is blocked anyway, this user error is mostly for developers that try to call this function
raise UserError(_("There is no cash register in this session."))
self.cash_register_id.balance_end_real = counted_cash
self.cash_register_balance_end_real = counted_cash
return {'successful': True}
@@ -540,7 +531,7 @@ class PosSession(models.Model):
cash_in_count = 0
cash_out_count = 0
cash_in_out_list = []
for cash_move in self.cash_register_id.line_ids.sorted('create_date'):
for cash_move in self.statement_line_ids.sorted('create_date'):
if cash_move.amount > 0:
cash_in_count += 1
name = f'Cash in {cash_in_count}'
@@ -562,9 +553,10 @@ class PosSession(models.Model):
'opening_notes': self.opening_notes,
'default_cash_details': {
'name': default_cash_payment_method_id.name,
'amount': self.cash_register_id.balance_start + total_default_cash_payment_amount +
sum(self.cash_register_id.line_ids.mapped('amount')),
'opening': self.cash_register_id.balance_start,
'amount': self.cash_register_balance_start
+ total_default_cash_payment_amount
+ self.cash_real_transaction,
'opening': self.cash_register_balance_start,
'payment_amount': total_default_cash_payment_amount,
'moves': cash_in_out_list,
'id': default_cash_payment_method_id.id
@@ -634,7 +626,6 @@ class PosSession(models.Model):
Side-effects include:
- setting self.move_id to the created account.move record
- creating and validating account.bank.statement for cash payments
- reconciling cash receivable lines, invoice receivable lines and stock output lines
"""
account_move = self.env['account.move'].create({
@@ -964,42 +955,63 @@ class PosSession(models.Model):
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.
# `split_cash_statement_lines` maps `statement` -> split cash statement lines
# `combine_cash_statement_lines` maps `statement` -> combine cash statement lines
# `split_cash_receivable_lines` maps `statement` -> split cash receivable lines
# `combine_cash_receivable_lines` maps `statement` -> combine cash receivable lines
# `split_cash_statement_lines` maps `journal` -> split cash statement lines
# `combine_cash_statement_lines` maps `journal` -> combine cash statement lines
# `split_cash_receivable_lines` maps `journal` -> split cash receivable lines
# `combine_cash_receivable_lines` maps `journal` -> combine cash receivable lines
MoveLine = data.get('MoveLine')
split_receivables_cash = data.get('split_receivables_cash')
combine_receivables_cash = data.get('combine_receivables_cash')
statements_by_journal_id = {statement.journal_id.id: statement for statement in self.statement_ids}
# handle split cash payments
split_cash_statement_line_vals = defaultdict(list)
split_cash_receivable_vals = defaultdict(list)
split_cash_statement_line_vals = []
split_cash_receivable_vals = []
for payment, amounts in split_receivables_cash.items():
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']))
journal_id = payment.payment_method_id.journal_id.id
split_cash_statement_line_vals.append(
self._get_split_statement_line_vals(
journal_id,
amounts['amount'],
payment
)
)
split_cash_receivable_vals.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)
combine_cash_statement_line_vals = []
combine_cash_receivable_vals = []
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.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']))
combine_cash_statement_line_vals.append(
self._get_combine_statement_line_vals(
payment_method.journal_id.id,
amounts['amount'],
payment_method
)
)
combine_cash_receivable_vals.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']
split_cash_statement_lines = {}
combine_cash_statement_lines = {}
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]).mapped('move_id.line_ids').filtered(lambda line: line.account_id.account_type == 'asset_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.account_type == 'asset_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])
split_cash_statement_lines = BankStatementLine.create(split_cash_statement_line_vals).mapped('move_id.line_ids').filtered(lambda line: line.account_id.account_type == 'asset_receivable')
combine_cash_statement_lines = BankStatementLine.create(combine_cash_statement_line_vals).mapped('move_id.line_ids').filtered(lambda line: line.account_id.account_type == 'asset_receivable')
split_cash_receivable_lines = MoveLine.create(split_cash_receivable_vals)
combine_cash_receivable_lines = MoveLine.create(combine_cash_receivable_vals)
data.update(
{'split_cash_statement_lines': split_cash_statement_lines,
@@ -1069,31 +1081,22 @@ class PosSession(models.Model):
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]
| 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 if account.reconcile]
for lines in lines_by_account:
lines.reconcile()
# We try to validate the statement after the reconciliation is done
# because validating the statement requires each statement line to be
# reconciled.
# Furthermore, if the validation failed, which is caused by unreconciled
# cash difference statement line, we just ignore that. Leaving the statement
# not yet validated. Manual reconciliation and validation should be made
# by the user in the accounting app.
try:
statement.button_validate()
except UserError:
pass
if not self.config_id.cash_control:
self.cash_register_balance_end_real = self.cash_register_balance_end
all_lines = (
split_cash_statement_lines
| combine_cash_statement_lines
| split_cash_receivable_lines
| combine_cash_receivable_lines
)
all_lines.filtered(lambda line: line.move_id.state != 'posted').move_id._post(soft=False)
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 if account.reconcile]
for lines in lines_by_account:
lines.reconcile()
for payment_method, lines in payment_method_to_receivable_lines.items():
receivable_account = self._get_receivable_account(payment_method)
@@ -1247,24 +1250,24 @@ 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_combine_statement_line_vals(self, statement, amount, payment_method):
def _get_combine_statement_line_vals(self, journal_id, amount, payment_method):
return {
'date': fields.Date.context_today(self),
'amount': amount,
'payment_ref': self.name,
'statement_id': statement.id,
'journal_id': statement.journal_id.id,
'pos_session_id': self.id,
'journal_id': journal_id,
'counterpart_account_id': self._get_receivable_account(payment_method).id,
}
def _get_split_statement_line_vals(self, statement, amount, payment):
def _get_split_statement_line_vals(self, journal_id, 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,
'payment_ref': payment.name,
'pos_session_id': self.id,
'journal_id': journal_id,
'counterpart_account_id': accounting_partner.property_account_receivable_id.id,
'partner_id': accounting_partner.id,
}
@@ -1397,12 +1400,13 @@ class PosSession(models.Model):
return self.currency_id._convert(amount, self.company_id.currency_id, self.company_id, date, round=round)
def show_cash_register(self):
self.ensure_one()
return {
'name': _('Cash register for %s') % (self.cash_register_id.name, ),
'name': _('Cash register for %s') % (self.name, ),
'type': 'ir.actions.act_window',
'res_model': 'account.bank.statement',
'view_mode': 'form',
'res_id': self.cash_register_id.id,
'res_model': 'account.bank.statement.line',
'view_mode': 'tree',
'domain': [('pos_session_id', '=', self.id)],
}
def show_journal_items(self):
@@ -1438,7 +1442,7 @@ class PosSession(models.Model):
invoices = self.mapped('order_ids.account_move')
invoice_payments = self.mapped('order_ids.payment_ids.account_move_id')
stock_account_moves = pickings.mapped('move_ids.account_move_ids')
cash_moves = self.cash_register_id.line_ids.mapped('move_id')
cash_moves = self.statement_line_ids.mapped('move_id')
bank_payment_moves = self.bank_payment_ids.mapped('move_id')
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
@@ -1472,8 +1476,8 @@ class PosSession(models.Model):
def set_cashbox_pos(self, cashbox_value, notes):
self.state = 'opened'
self.opening_notes = notes
difference = cashbox_value - self.cash_register_id.balance_start
self.cash_register_id.balance_start = cashbox_value
difference = cashbox_value - self.cash_register_balance_start
self.cash_register_balance_start = cashbox_value
self._post_cash_details_message('Opening', difference, notes)
def _post_cash_details_message(self, state, difference, notes):
@@ -1488,8 +1492,8 @@ class PosSession(models.Model):
if message:
self.env['mail.message'].create({
'body': message,
'model': 'account.bank.statement',
'res_id': self.cash_register_id.id,
'model': self._name,
'res_id': self.id,
})
self.message_post(body=message)
@@ -1534,10 +1538,19 @@ class PosSession(models.Model):
def try_cash_in_out(self, _type, amount, reason, extras):
sign = 1 if _type == 'in' else -1
self.env['cash.box.out']\
.with_context({'active_model': 'pos.session', 'active_ids': self.ids})\
.create({'amount': sign * amount, 'name': reason})\
.run()
sessions = self.filtered('cash_journal_id')
if not sessions:
raise UserError(_("There is no cash payment method for this PoS Session"))
for session in sessions:
self.env['account.bank.statement.line'].create({
'pos_session_id': session.id,
'journal_id': session.cash_journal_id.id,
'amount': sign * amount,
'date': fields.Date.context_today(self),
'payment_ref': '-'.join([session.name, extras['translatedType'], reason]),
})
message_content = [f"Cash {extras['translatedType']}", f'- Amount: {extras["formattedAmount"]}']
if reason:
message_content.append(f'- Reason: {reason}')
@@ -1636,8 +1649,6 @@ class PosSession(models.Model):
'pos.payment.method',
'account.fiscal.position',
]
if self.config_id.cash_control:
models_to_load.append('account.bank.statement')
return models_to_load
@@ -1720,7 +1731,7 @@ class PosSession(models.Model):
'domain': [('id', '=', self.id)],
'fields': [
'id', 'name', 'user_id', 'config_id', 'start_at', 'stop_at', 'sequence_number',
'payment_method_ids', 'cash_register_id', 'state', 'update_stock_at_closing'
'payment_method_ids', 'state', 'update_stock_at_closing'
],
},
}
@@ -1831,12 +1842,6 @@ class PosSession(models.Model):
return pricelists
def _loader_params_account_bank_statement(self):
return {'search_params': {'domain': [('id', '=', self.cash_register_id.id)], 'fields': ['id', 'balance_start']}}
def _get_pos_ui_account_bank_statement(self, params):
return self.env['account.bank.statement'].search_read(**params['search_params'])[0]
def _loader_params_product_category(self):
return {'search_params': {'domain': [], 'fields': ['name', 'parent_id']}}
@@ -43,15 +43,12 @@ access_barcode_nomenclature_pos_user,barcode.nomenclature.pos.user,barcodes.mode
access_barcode_nomenclature_pos_manager,barcode.nomenclature.pos.manager,barcodes.model_barcode_nomenclature,group_pos_manager,1,1,1,1
access_barcode_rule_pos_user,barcode.rule.pos.user,barcodes.model_barcode_rule,group_pos_user,1,0,0,0
access_barcode_rule_pos_manager,barcode.rule.pos.manager,barcodes.model_barcode_rule,group_pos_manager,1,1,1,1
access_bank_statement_cashbox_user,account.bank.statement.cashbox user,account.model_account_bank_statement_cashbox,group_pos_user,1,1,1,0
access_account_cashbox_line_user,account.cashbox.line user,account.model_account_cashbox_line,group_pos_user,1,1,1,1
access_decimal_precision_user,decimal.precision,base.model_decimal_precision,group_pos_user,1,0,0,0
access_pos_payment_user,pos.payment user,model_pos_payment,group_pos_user,1,1,1,1
access_pos_payment_method_user,pos.payment.method user,model_pos_payment_method,group_pos_user,1,0,0,0
access_pos_payment_method_manager,pos.payment.method manager,model_pos_payment_method,group_pos_manager,1,1,1,1
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
access_account_cash_rounding_pos_user,account.cash.rounding pos_user,account.model_account_cash_rounding,group_pos_user,1,0,0,0
access_pos_bill,pos.bill pos_user,model_pos_bill,group_pos_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
43 access_barcode_nomenclature_pos_manager barcode.nomenclature.pos.manager barcodes.model_barcode_nomenclature group_pos_manager 1 1 1 1
44 access_barcode_rule_pos_user barcode.rule.pos.user barcodes.model_barcode_rule group_pos_user 1 0 0 0
45 access_barcode_rule_pos_manager barcode.rule.pos.manager barcodes.model_barcode_rule group_pos_manager 1 1 1 1
access_bank_statement_cashbox_user account.bank.statement.cashbox user account.model_account_bank_statement_cashbox group_pos_user 1 1 1 0
access_account_cashbox_line_user account.cashbox.line user account.model_account_cashbox_line group_pos_user 1 1 1 1
46 access_decimal_precision_user decimal.precision base.model_decimal_precision group_pos_user 1 0 0 0
47 access_pos_payment_user pos.payment user model_pos_payment group_pos_user 1 1 1 1
48 access_pos_payment_method_user pos.payment.method user model_pos_payment_method group_pos_user 1 0 0 0
49 access_pos_payment_method_manager pos.payment.method manager model_pos_payment_method group_pos_manager 1 1 1 1
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
access_money_in_out_wizard access.money.in.out.wizard model_cash_box_out point_of_sale.group_pos_user 1 1 1 0
52 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
53 access_account_cash_rounding_pos_user account.cash.rounding pos_user account.model_account_cash_rounding group_pos_user 1 0 0 0
54 access_pos_bill pos.bill pos_user model_pos_bill group_pos_user 1 1 1 1
@@ -34,7 +34,7 @@
<field name="name">Point Of Sale Bank Statement Line POS User</field>
<field name="model_id" ref="account.model_account_bank_statement_line" />
<field name="groups" eval="[(4, ref('group_pos_user'))]"/>
<field name="domain_force">[('statement_id.pos_session_id', '!=', False)]</field>
<field name="domain_force">[('pos_session_id', '!=', False)]</field>
</record>
<record id="rule_pos_bank_statement_line_account_user" model="ir.rule">
<field name="name">Point Of Sale Bank Statement Line Accountant</field>
@@ -42,12 +42,6 @@
<field name="groups" eval="[(4, ref('account.group_account_invoice'))]"/>
<field name="domain_force">[(1, '=', 1)]</field>
</record>
<record id="rule_pos_cashbox_line_accountant" model="ir.rule">
<field name="name">Point Of Sale Cashbox Line Accountant</field>
<field name="model_id" ref="account.model_account_cashbox_line" />
<field name="groups" eval="[(4, ref('account.group_account_invoice'))]"/>
<field name="domain_force">[(1, '=', 1)]</field>
</record>
<record id="rule_pos_multi_company" model="ir.rule">
<field name="name">Point Of Sale Order</field>
<field name="model_id" ref="model_pos_order" />
@@ -12,13 +12,13 @@ odoo.define('point_of_sale.CashOpeningPopup', function(require) {
this.manualInputCashCount = null;
this.state = useState({
notes: "",
openingCash: this.env.pos.bank_statement.balance_start || 0,
openingCash: this.env.pos.pos_session.cash_register_balance_start || 0,
displayMoneyDetailsPopup: false,
});
}
//@override
async confirm() {
this.env.pos.bank_statement.balance_start = this.state.openingCash;
this.env.pos.pos_session.cash_register_balance_start = this.state.openingCash;
this.env.pos.pos_session.state = 'opened';
this.rpc({
model: 'pos.session',
@@ -198,7 +198,6 @@ class PosGlobalState extends PosModel {
this.user = loadedData['res.users'];
this.pricelists = loadedData['product.pricelist'];
this.default_pricelist = loadedData['default_pricelist'];
this.bank_statement = loadedData['account.bank.statement'];
this.currency = loadedData['res.currency'];
this.db.add_categories(loadedData['pos.category']);
this._loadProductProduct(loadedData['product.product']);
+1 -1
View File
@@ -686,7 +686,7 @@ class TestPoSCommon(ValuationReconciliationTestCommon):
_logger.info("DONE: Check of the session's account move.")
# check expected cash journal entries
for statement_line in pos_session.cash_register_id.line_ids:
for statement_line in pos_session.statement_line_ids:
def statement_line_predicate(args):
return tools.float_is_zero(statement_line.amount - args[0], precision_rounding=currency_rounding)
self._find_then_assert_values(statement_line.move_id, expected_values['cash_statement'], statement_line_predicate)
@@ -105,7 +105,6 @@ class TestAngloSaxonFlow(TestAngloSaxonCommon):
# I close the current session to generate the journal entries
current_session_id = self.pos_config.current_session_id
current_session_id._check_pos_session_balance()
current_session_id.post_closing_cash_details(450.0)
current_session_id.close_session_from_ui()
self.assertEqual(current_session_id.state, 'closed', 'Check that session is closed')
@@ -552,48 +552,49 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
"""In order to test the reports on Bank Statement defined in point_of_sale module, I create a bank statement line, confirm it and print the reports"""
# I select the period and journal for the bank statement
context_journal = {'journal_type': 'bank'}
self.assertTrue(self.AccountBankStatement.with_context(
context_journal)._default_journal(), 'Journal has not been selected')
journal = self.env['account.journal'].create({
'name': 'Bank Test',
'code': 'BNKT',
'type': 'bank',
'company_id': self.env.company.id,
})
# I create a bank statement with Opening and Closing balance 0.
account_statement = self.AccountBankStatement.create({
'balance_start': 0.0,
'balance_end_real': 0.0,
'date': time.strftime('%Y-%m-%d'),
'journal_id': journal.id,
'company_id': self.env.company.id,
'name': 'pos session test',
})
# I create bank statement line
account_statement_line = self.AccountBankStatementLine.create({
'amount': 1000,
'partner_id': self.partner4.id,
'statement_id': account_statement.id,
'payment_ref': 'EXT001'
})
# I modify the bank statement and set the Closing Balance.
account_statement.write({
'balance_end_real': 1000.0,
})
# I reconcile the bank statement.
new_aml_dicts = [{
'account_id': self.partner4.property_account_receivable_id.id,
'name': "EXT001",
'credit': 1000.0,
'debit': 0.0,
}]
# I confirm the bank statement using Confirm button
self.AccountBankStatement.button_validate()
# FIXME:poma
#
# context_journal = {'journal_type': 'bank'}
# self.assertTrue(self.AccountBankStatement.with_context(
# context_journal)._default_journal(), 'Journal has not been selected')
# journal = self.env['account.journal'].create({
# 'name': 'Bank Test',
# 'code': 'BNKT',
# 'type': 'bank',
# 'company_id': self.env.company.id,
# })
# # I create a bank statement with Opening and Closing balance 0.
# account_statement = self.AccountBankStatement.create({
# 'balance_start': 0.0,
# 'balance_end_real': 0.0,
# 'date': time.strftime('%Y-%m-%d'),
# 'journal_id': journal.id,
# 'company_id': self.env.company.id,
# 'name': 'pos session test',
# })
# # I create bank statement line
# account_statement_line = self.AccountBankStatementLine.create({
# 'amount': 1000,
# 'partner_id': self.partner4.id,
# 'statement_id': account_statement.id,
# 'payment_ref': 'EXT001'
# })
# # I modify the bank statement and set the Closing Balance.
# account_statement.write({
# 'balance_end_real': 1000.0,
# })
#
# # I reconcile the bank statement.
# new_aml_dicts = [{
# 'account_id': self.partner4.property_account_receivable_id.id,
# 'name': "EXT001",
# 'credit': 1000.0,
# 'debit': 0.0,
# }]
#
# # I confirm the bank statement using Confirm button
#
# self.AccountBankStatement.button_validate()
def test_create_from_ui(self):
"""
@@ -822,18 +822,16 @@ class TestPoSBasicConfig(TestPoSCommon):
session.post_closing_cash_details(amount_paid)
session.close_session_from_ui()
cash_register = session.cash_register_id
self.assertEqual(cash_register.balance_start, 0)
self.assertEqual(cash_register.balance_end_real, amount_paid)
self.assertEqual(session.cash_register_balance_start, 0)
self.assertEqual(session.cash_register_balance_end_real, amount_paid)
# Open/Close session without any order in cash control
self.open_new_session(amount_paid)
session = self.pos_session
session.post_closing_cash_details(amount_paid)
session.close_session_from_ui()
cash_register = session.cash_register_id
self.assertEqual(cash_register.balance_start, amount_paid)
self.assertEqual(cash_register.balance_end_real, amount_paid)
self.assertEqual(session.cash_register_balance_start, amount_paid)
self.assertEqual(session.cash_register_balance_end_real, amount_paid)
self.assertEqual(self.config.last_session_closing_cash, amount_paid)
def test_start_balance_with_two_pos(self):
@@ -844,7 +842,7 @@ class TestPoSBasicConfig(TestPoSCommon):
self.open_new_session()
session = self.pos_session
session.set_cashbox_pos(pos_data['amount_paid'], False)
self.assertEqual(session.cash_register_id.balance_start, pos_data['amount_paid'])
self.assertEqual(session.cash_register_balance_start, pos_data['amount_paid'])
pos01_config = self.config
pos02_config = pos01_config.copy()
@@ -1,16 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_bank_statement_pos_session" model="ir.ui.view">
<field name="name">view.bank.statement.pos.session</field>
<field name="model">account.bank.statement</field>
<field name="mode">primary</field>
<field name="priority">1000</field>
<field name="inherit_id" ref="account.view_bank_statement_form"/>
<field name="arch" type="xml">
<xpath expr="//button[@name='button_validate_or_action']" position="replace"/>
</field>
</record>
<record id="view_account_journal_pos_user_form" model="ir.ui.view">
<field name="name">account.journal.pos.user.form.inherit</field>
<field name="model">account.journal</field>
@@ -1,28 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!--Account cashbox line-->
<record id="account_cashbox_line_view_tree" model="ir.ui.view">
<field name="name">account.cashbox.line.tree</field>
<field name="model">account.cashbox.line</field>
<field name="arch" type="xml">
<tree string="Cashbox balance" editable="top">
<field name="coin_value"/>
<field name="number"/>
<field name="subtotal"/>
</tree>
</field>
</record>
<record id="account_cashbox_line_action" model="ir.actions.act_window">
<field name="name">Opening/Closing Values</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">account.cashbox.line</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="account_cashbox_line_view_tree"/>
</record>
<record id="pos_config_view_form" model="ir.ui.view">
<field name="name">pos.config.form.view</field>
<field name="model">pos.config</field>
@@ -15,7 +15,6 @@
<field name="state" widget="statusbar" statusbar_visible="opened,closing_control,closed" nolabel="1" />
</header>
<sheet>
<field name="cash_register_id" invisible="1"/>
<field name="failed_pickings" invisible="1"/>
<field name="rescue" invisible="1"/>
<div class="oe_button_box" name="button_box">
@@ -46,6 +45,7 @@
groups="account.group_account_readonly"
>
</button>
<!--todo:poma find a replacement -->
<button
name="show_cash_register"
type="object"
-1
View File
@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import pos_box
from . import pos_details
from . import pos_payment
from . import pos_close_session_wizard
-35
View File
@@ -1,35 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, _
from odoo.exceptions import UserError
from odoo.addons.account.wizard.pos_box import CashBox
class PosBox(CashBox):
_register = False
def run(self):
active_model = self.env.context.get('active_model', False)
active_ids = self.env.context.get('active_ids', [])
if active_model == 'pos.session':
bank_statements = [session.cash_register_id for session in self.env[active_model].browse(active_ids) if session.cash_register_id]
if not bank_statements:
raise UserError(_("There is no cash register for this PoS Session"))
return self._run(bank_statements)
else:
return super(PosBox, self).run()
class PosBoxOut(PosBox):
_inherit = 'cash.box.out'
def _calculate_values_for_statement_line(self, record):
values = super(PosBoxOut, self)._calculate_values_for_statement_line(record)
active_model = self.env.context.get('active_model', False)
active_ids = self.env.context.get('active_ids', [])
if active_model == 'pos.session' and active_ids:
values['ref'] = self.env[active_model].browse(active_ids)[0].name
return values
-9
View File
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="action_pos_box_out" model="ir.actions.act_window">
<field name="name">Take Money In/Out</field>
<field name="res_model">cash.box.out</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</odoo>
@@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models, _
from odoo.exceptions import UserError
class PosOpenStatement(models.TransientModel):
_name = 'pos.open.statement'
_description = 'Point of Sale Open Statement'
def open_statement(self):
self.ensure_one()
BankStatement = self.env['account.bank.statement']
journals = self.env['account.journal'].search([('journal_user', '=', True)])
if not journals:
raise UserError(_('You have to define which payment method must be available in the point of sale by reusing existing bank and cash through "Accounting / Configuration / Journals / Journals". Select a journal and check the field "PoS Payment Method" from the "Point of Sale" tab. You can also create new payment methods directly from menu "PoS Backend / Configuration / Payment Methods".'))
for journal in journals:
if journal.sequence_id:
number = journal.sequence_id.next_by_id()
else:
raise UserError(_("No sequence defined on the journal"))
BankStatement += BankStatement.create({'journal_id': journal.id, 'user_id': self.env.uid, 'name': number})
tree_id = self.env.ref('account.view_bank_statement_tree').id
form_id = self.env.ref('account.view_bank_statement_form').id
search_id = self.env.ref('account.view_bank_statement_search').id
return {
'type': 'ir.actions.act_window',
'name': _('List of Cash Registers'),
'view_mode': 'tree,form',
'res_model': 'account.bank.statement',
'domain': str([('id', 'in', BankStatement.ids)]),
'views': [(tree_id, 'tree'), (form_id, 'form')],
'search_view_id': [search_id],
}