From fcd8cba65b4c9b628b120d71fb54e447614b99ec Mon Sep 17 00:00:00 2001 From: Saran440 Date: Wed, 1 Jul 2026 19:50:20 +0700 Subject: [PATCH] [FIX] hr_expense_petty_cash: reset draft with tax wrong --- hr_expense_petty_cash/models/account_move.py | 31 +++++++++++- .../models/hr_expense_sheet.py | 8 ---- .../tests/test_hr_expense_petty_cash.py | 47 +++++++++---------- 3 files changed, 51 insertions(+), 35 deletions(-) diff --git a/hr_expense_petty_cash/models/account_move.py b/hr_expense_petty_cash/models/account_move.py index 321ba8650..8187f0f7c 100644 --- a/hr_expense_petty_cash/models/account_move.py +++ b/hr_expense_petty_cash/models/account_move.py @@ -13,7 +13,36 @@ class AccountMove(models.Model): def action_post(self): self._check_petty_cash_amount() - return super().action_post() + res = super().action_post() + # Posting a petty cash clearing entry pays the expense sheet directly, + # there is no payment to register for it. + petty_cash_sheets = ( + self.sudo() + .mapped("expense_sheet_id") + .filtered(lambda sheet: sheet.payment_mode == "petty_cash") + ) + if petty_cash_sheets: + petty_cash_sheets.write( + {"state": "done", "amount_residual": 0.0, "payment_state": "paid"} + ) + return res + + def button_draft(self): + if self.env.context.get("skip_invoice_sync"): + return super().button_draft() + # Petty cash clearing entries keep an explicit balance + explicit tax + # lines. The tax recompute done by the sync when resetting to draft + # would otherwise strip the tax twice and create a spurious + # "Automatic Balancing Line". + petty_cash_moves = self.filtered( + lambda m: m.move_type == "entry" + and m.expense_sheet_id + and m.expense_sheet_id.payment_mode == "petty_cash" + ) + if not petty_cash_moves: + return super().button_draft() + (self - petty_cash_moves).button_draft() + return petty_cash_moves.with_context(skip_invoice_sync=True).button_draft() def _prepare_product_base_line_for_taxes_computation(self, product_line): results = super()._prepare_product_base_line_for_taxes_computation(product_line) diff --git a/hr_expense_petty_cash/models/hr_expense_sheet.py b/hr_expense_petty_cash/models/hr_expense_sheet.py index 799af255b..b5ae7eb42 100644 --- a/hr_expense_petty_cash/models/hr_expense_sheet.py +++ b/hr_expense_petty_cash/models/hr_expense_sheet.py @@ -139,11 +139,3 @@ def _prepare_bills_vals(self): } ) return res - - def action_sheet_move_post(self): - res = super().action_sheet_move_post() - paid_petty_cash = self.filtered(lambda m: m.payment_mode == "petty_cash") - paid_petty_cash.write( - {"state": "done", "amount_residual": 0.0, "payment_state": "paid"} - ) - return res diff --git a/hr_expense_petty_cash/tests/test_hr_expense_petty_cash.py b/hr_expense_petty_cash/tests/test_hr_expense_petty_cash.py index 895e8f84c..94f6ade3f 100644 --- a/hr_expense_petty_cash/tests/test_hr_expense_petty_cash.py +++ b/hr_expense_petty_cash/tests/test_hr_expense_petty_cash.py @@ -258,9 +258,6 @@ def test_02_create_expense_petty_cash(self): expense_petty_cash_2 = self._create_expense( 200.0, self.employee_1, "petty_cash", self.petty_cash_holder_2 ) - expense_petty_cash_3 = self._create_expense( - 100.0, self.employee_2, "petty_cash", self.petty_cash_holder_2 - ) expense_report = expense_own + expense_petty_cash + expense_petty_cash_2 # Check expenses must have 1 petty cash holder only with self.assertRaises(ValidationError): @@ -283,26 +280,7 @@ def test_02_create_expense_petty_cash(self): sheet.action_submit_sheet() self.assertEqual(sheet.state, "submit") sheet.action_approve_expense_sheets() - # Check if the sheet is approved (could be 'approve' or - # 'post' depending on configuration) - self.assertIn(sheet.state, ["approve", "post"]) - # Check state != draft, many employee and don't have product - with self.assertRaises(UserError): - expense_petty_cash.action_submit_expenses() - expense_test = expense_petty_cash_2 + expense_petty_cash_3 - with self.assertRaises(UserError): - expense_test.action_submit_expenses() - expense_petty_cash_3.product_id = False - with self.assertRaises(UserError): - expense_petty_cash_3.action_submit_expenses() - # Check if journal entries were created and sheet is in final state - if sheet.state == "approve": - # If still in approve state, we need to post manually - sheet.action_sheet_move_post() - self.assertEqual(sheet.state, "post") - else: - # If already posted, just verify - self.assertEqual(sheet.state, "post") + self.assertEqual(sheet.state, "done") self.assertTrue(sheet.account_move_ids.id) self.assertEqual(self.petty_cash_holder.petty_cash_balance, 600.0) @@ -371,9 +349,9 @@ def test_05_petty_cash_with_analytic_and_tax(self): sheet = self._create_expense_sheet(expense) sheet.action_submit_sheet() sheet.action_approve_expense_sheets() - if sheet.state == "approve": - sheet.action_sheet_move_post() - self.assertEqual(sheet.state, "post") + # Petty cash sheets are paid automatically upon approval. + self.assertEqual(sheet.state, "done") + self.assertEqual(sheet.payment_state, "paid") move = sheet.account_move_ids self.assertEqual(move.move_type, "entry") @@ -405,3 +383,20 @@ def test_05_petty_cash_with_analytic_and_tax(self): tax_lines, "Petty cash entry should generate a tax line.", ) + + # Resetting the move to draft must not create a spurious + # "Automatic Balancing Line" and must keep the same line count. + line_count = len(move.line_ids) + move.button_draft() + self.assertEqual(len(move.line_ids), line_count) + self.assertFalse( + move.line_ids.filtered(lambda line: line.name == "Automatic Balancing Line") + ) + self.assertEqual(sheet.state, "approve") + # Re-posting the move marks the petty cash sheet done/paid again + # without creating any extra line. + move.action_post() + self.assertEqual(move.state, "posted") + self.assertEqual(len(move.line_ids), line_count) + self.assertEqual(sheet.state, "done") + self.assertEqual(sheet.payment_state, "paid")