[FIX] hr_expense: Expense not cancelled

Current behavior:
When cancelling an expense payment, the expense state was not modified and was still 'paid' when it should be 'refused'.

Steps to reproduce:
- Create an expense and post the expense and register a payment for the expense.
- Then go the vendor payments and cancel the associated vendor payment.
- The expense still shows as paid in the expenses page.

opw-2711383

closes odoo/odoo#82496

X-original-commit: 16459957d23f4bdfddfe96d72ef96cbdc6fec539
Signed-off-by: Laurent Smet <las@odoo.com>
Signed-off-by: Engels Robin (roen) <roen@odoo.com>
This commit is contained in:
roen-odoo
2022-01-11 16:18:49 +00:00
parent 683c02b099
commit ca6cd0362e
3 changed files with 103 additions and 48 deletions
@@ -538,41 +538,29 @@ class AccountPaymentRegister(models.TransientModel):
'destination_account_id': batch_result['lines'][0].account_id.id
}
def _create_payments(self):
self.ensure_one()
batches = self._get_batches()
edit_mode = self.can_edit_wizard and (len(batches[0]['lines']) == 1 or self.group_payment)
def _init_payments(self, to_process, edit_mode=False):
""" Create the payments.
to_reconcile = []
if edit_mode:
payment_vals = self._create_payment_vals_from_wizard()
payment_vals_list = [payment_vals]
to_reconcile.append(batches[0]['lines'])
else:
# Don't group payments: Create one batch per move.
if not self.group_payment:
new_batches = []
for batch_result in batches:
for line in batch_result['lines']:
new_batches.append({
**batch_result,
'lines': line,
})
batches = new_batches
:param to_process: A list of python dictionary, one for each payment to create, containing:
* create_vals: The values used for the 'create' method.
* to_reconcile: The journal items to perform the reconciliation.
* batch: A python dict containing everything you want about the source journal items
to which a payment will be created (see '_get_batches').
:param edit_mode: Is the wizard in edition mode.
"""
payment_vals_list = []
for batch_result in batches:
payment_vals_list.append(self._create_payment_vals_from_batch(batch_result))
to_reconcile.append(batch_result['lines'])
payments = self.env['account.payment'].create([x['create_vals'] for x in to_process])
payments = self.env['account.payment'].create(payment_vals_list)
for payment, vals in zip(payments, to_process):
vals['payment'] = payment
# If payments are made using a currency different than the source one, ensure the balance match exactly in
# order to fully paid the source journal items.
# For example, suppose a new currency B having a rate 100:1 regarding the company currency A.
# If you try to pay 12.15A using 0.12B, the computed balance will be 12.00A for the payment instead of 12.15A.
if edit_mode:
lines = vals['to_reconcile']
# If payments are made using a currency different than the source one, ensure the balance match exactly in
# order to fully paid the source journal items.
# For example, suppose a new currency B having a rate 100:1 regarding the company currency A.
# If you try to pay 12.15A using 0.12B, the computed balance will be 12.00A for the payment instead of 12.15A.
if edit_mode:
for payment, lines in zip(payments, to_reconcile):
# Batches are made using the same currency so making 'lines.currency_id' is ok.
if payment.currency_id != lines.currency_id:
liquidity_lines, counterpart_lines, writeoff_lines = payment._seek_for_lines()
@@ -603,23 +591,78 @@ class AccountPaymentRegister(models.TransientModel):
(1, debit_lines[0].id, {'debit': debit_lines[0].debit + delta_balance}),
(1, credit_lines[0].id, {'credit': credit_lines[0].credit + delta_balance}),
]})
return payments
def _post_payments(self, to_process, edit_mode=False):
""" Post the newly created payments.
:param to_process: A list of python dictionary, one for each payment to create, containing:
* create_vals: The values used for the 'create' method.
* to_reconcile: The journal items to perform the reconciliation.
* batch: A python dict containing everything you want about the source journal items
to which a payment will be created (see '_get_batches').
:param edit_mode: Is the wizard in edition mode.
"""
payments = self.env['account.payment']
for vals in to_process:
payments |= vals['payment']
payments.action_post()
def _reconcile_payments(self, to_process, edit_mode=False):
""" Reconcile the payments.
:param to_process: A list of python dictionary, one for each payment to create, containing:
* create_vals: The values used for the 'create' method.
* to_reconcile: The journal items to perform the reconciliation.
* batch: A python dict containing everything you want about the source journal items
to which a payment will be created (see '_get_batches').
:param edit_mode: Is the wizard in edition mode.
"""
domain = [('account_internal_type', 'in', ('receivable', 'payable')), ('reconciled', '=', False)]
for payment, lines in zip(payments, to_reconcile):
for vals in to_process:
payment_lines = vals['payment'].line_ids.filtered_domain(domain)
lines = vals['to_reconcile']
# When using the payment tokens, the payment could not be posted at this point (e.g. the transaction failed)
# and then, we can't perform the reconciliation.
if payment.state != 'posted':
continue
payment_lines = payment.line_ids.filtered_domain(domain)
for account in payment_lines.account_id:
(payment_lines + lines)\
.filtered_domain([('account_id', '=', account.id), ('reconciled', '=', False)])\
.reconcile()
def _create_payments(self):
self.ensure_one()
batches = self._get_batches()
edit_mode = self.can_edit_wizard and (len(batches[0]['lines']) == 1 or self.group_payment)
to_process = []
if edit_mode:
payment_vals = self._create_payment_vals_from_wizard()
to_process.append({
'create_vals': payment_vals,
'to_reconcile': batches[0]['lines'],
'batch': batches[0],
})
else:
# Don't group payments: Create one batch per move.
if not self.group_payment:
new_batches = []
for batch_result in batches:
for line in batch_result['lines']:
new_batches.append({
**batch_result,
'lines': line,
})
batches = new_batches
for batch_result in batches:
to_process.append({
'create_vals': self._create_payment_vals_from_batch(batch_result),
'to_reconcile': batch_result['lines'],
'batch': batch_result,
})
payments = self._init_payments(to_process, edit_mode=edit_mode)
self._post_payments(to_process, edit_mode=edit_mode)
self._reconcile_payments(to_process, edit_mode=edit_mode)
return payments
def action_create_payments(self):
+5 -1
View File
@@ -344,5 +344,9 @@ class TestExpenses(TestExpenseCommon):
sheet.action_sheet_move_create()
action_data = sheet.action_register_payment()
wizard = Form(self.env['account.payment.register'].with_context(action_data['context'])).save()
wizard.action_create_payments()
action = wizard.action_create_payments()
self.assertEqual(sheet.state, 'done', 'all account.move.line linked to expenses must be reconciled after payment')
move = self.env['account.payment'].browse(action['res_id']).move_id
move.button_cancel()
self.assertEqual(sheet.state, 'cancel', 'Sheet state must be cancel when the payment linked to that sheet is canceled')
@@ -19,13 +19,21 @@ class AccountPaymentRegister(models.TransientModel):
res['partner_bank_id'] = expense_sheet.employee_id.bank_account_id.id or line.partner_id.bank_ids and line.partner_id.bank_ids.ids[0]
return res
def _create_payments(self):
# OVERRIDE to set the 'done' state on expense sheets.
payments = super()._create_payments()
expense_sheets = self.env['hr.expense.sheet'].search([('account_move_id', 'in', self.line_ids.move_id.ids)])
for expense_sheet in expense_sheets:
if expense_sheet.currency_id.is_zero(expense_sheet.amount_residual):
expense_sheet.state = 'done'
def _init_payments(self, to_process, edit_mode=False):
# OVERRIDE
payments = super()._init_payments(to_process, edit_mode=edit_mode)
for payment, vals in zip(payments, to_process):
expenses = vals['batch']['lines'].expense_id
if expenses:
payment.line_ids.write({'expense_id': expenses[0].id})
return payments
def _reconcile_payments(self, to_process, edit_mode=False):
# OVERRIDE
res = super()._reconcile_payments(to_process, edit_mode=edit_mode)
for vals in to_process:
expense_sheets = vals['batch']['lines'].expense_id.sheet_id
for expense_sheet in expense_sheets:
if expense_sheet.currency_id.is_zero(expense_sheet.amount_residual):
expense_sheet.state = 'done'
return res