From 7e2c132f1baec824a5dad0d0c29049fc2afc0293 Mon Sep 17 00:00:00 2001 From: Suherdy Yacob Date: Fri, 23 Jan 2026 13:03:23 +0700 Subject: [PATCH] fix calculation when using advance account --- fix_amount_issue.py | 20 +++--- models/account_payment.py | 89 ++++++++++++++++--------- models/payment_deduction_line.py | 2 +- tests/test_account_payment.py | 5 +- tests/test_batch_payment_integration.py | 3 +- wizard/payment_amount_fix_wizard.py | 33 +++++---- 6 files changed, 94 insertions(+), 58 deletions(-) diff --git a/fix_amount_issue.py b/fix_amount_issue.py index ea6a382..1d9a962 100755 --- a/fix_amount_issue.py +++ b/fix_amount_issue.py @@ -24,23 +24,25 @@ def fix_payment_amounts(): fixed_count = 0 for payment in payments: if payment.move_id: - # Find the counterpart line (payable/expense line with debit) - counterpart_lines = payment.move_id.line_ids.filtered( - lambda l: l.debit > 0 and l.account_id.account_type in ('liability_payable', 'expense') - ) + # Robust logic to find gross amount from moves + liquidity_lines, counterpart_lines, writeoff_lines = payment._seek_for_lines() + non_liquidity_lines = counterpart_lines + writeoff_lines - if counterpart_lines: - correct_amount = counterpart_lines[0].debit + if payment.payment_type == 'outbound': + gross_lines = non_liquidity_lines.filtered(lambda l: l.debit > 0) + else: + gross_lines = non_liquidity_lines.filtered(lambda l: l.credit > 0) + + if gross_lines: + correct_amount = sum(abs(l.amount_currency) for l in gross_lines) current_amount = payment.amount # Check if amount needs fixing (allow for small rounding differences) - if abs(current_amount - correct_amount) > 0.01: + if abs(current_amount - correct_amount) > 0.001: print(f"Payment {payment.name} (ID: {payment.id}):") print(f" Current amount: {current_amount}") print(f" Correct amount: {correct_amount}") print(f" Deductions: {payment.amount_substract}") - print(f" Current final: {payment.final_payment_amount}") - print(f" Expected final: {correct_amount - payment.amount_substract}") # Fix the amount using SQL to avoid triggering computed fields env.cr.execute( diff --git a/models/account_payment.py b/models/account_payment.py index 77f1c58..11b01eb 100755 --- a/models/account_payment.py +++ b/models/account_payment.py @@ -100,13 +100,18 @@ class AccountPayment(models.Model): if payment.amount_substract and payment.amount_substract > 0: # Get the correct amount from the journal entry if payment.move_id: - # Find the counterpart line (payable/expense line) - counterpart_lines = payment.move_id.line_ids.filtered( - lambda l: l.account_id.account_type in ('liability_payable', 'expense') and l.debit > 0 - ) - if counterpart_lines: - correct_amount = counterpart_lines[0].debit - if abs(payment.amount - correct_amount) > 0.01: # Allow for rounding differences + liquidity_lines, counterpart_lines, writeoff_lines = payment._seek_for_lines() + non_liquidity_lines = counterpart_lines + writeoff_lines + + # Find gross amount lines (Debits for outbound, Credits for inbound) + if payment.payment_type == 'outbound': + gross_lines = non_liquidity_lines.filtered(lambda l: l.debit > 0) + else: + gross_lines = non_liquidity_lines.filtered(lambda l: l.credit > 0) + + if gross_lines: + correct_amount = sum(abs(l.amount_currency) for l in gross_lines) + if abs(payment.amount - correct_amount) > 0.001: import logging _logger = logging.getLogger(__name__) _logger.info(f"Fixing amount for payment {payment.id}: {payment.amount} -> {correct_amount}") @@ -133,14 +138,19 @@ class AccountPayment(models.Model): fixed_count = 0 for payment in payments: if payment.move_id: - # Find the counterpart line (payable/expense line with debit) - counterpart_lines = payment.move_id.line_ids.filtered( - lambda l: l.debit > 0 and l.account_id.account_type in ('liability_payable', 'expense') - ) + # Robust logic to find gross amount from moves + liquidity_lines, counterpart_lines, writeoff_lines = payment._seek_for_lines() + non_liquidity_lines = counterpart_lines + writeoff_lines - if counterpart_lines: - correct_amount = counterpart_lines[0].debit - if abs(payment.amount - correct_amount) > 0.01: + # Find gross amount lines (Debits for outbound, Credits for inbound) + if payment.payment_type == 'outbound': + gross_lines = non_liquidity_lines.filtered(lambda l: l.debit > 0) + else: + gross_lines = non_liquidity_lines.filtered(lambda l: l.credit > 0) + + if gross_lines: + correct_amount = sum(abs(l.amount_currency) for l in gross_lines) + if abs(payment.amount - correct_amount) > 0.001: # Fix using SQL to avoid sync issues payment.env.cr.execute( "UPDATE account_payment SET amount = %s WHERE id = %s", @@ -165,38 +175,51 @@ class AccountPayment(models.Model): Override to handle synchronization when we have deductions. When we have a substract amount, the bank credit line is reduced to final_payment_amount, - but we want to keep the payment amount at the original value (not sync it down). + but we want to keep the payment amount at the original gross value. """ # For payments with deductions, we need to handle synchronization carefully for payment in self: if payment.amount_substract and payment.amount_substract > 0: - # Store the original amount before any synchronization + # Store potential original values original_amount = payment.amount - original_substract = payment.amount_substract - # Try to call parent sync but handle any errors + # Call parent sync try: super(AccountPayment, payment)._synchronize_from_moves(changed_fields) except Exception as e: - # If there's an error (like missing payable account when using expense_account_id), - # that's expected, so we just continue import logging _logger = logging.getLogger(__name__) - _logger.info(f"Sync error for payment {payment.id} (expected with deductions): {e}") + _logger.debug(f"Sync info for payment {payment.id} (handling deductions): {e}") - # After sync, ensure the amount is still correct - # The sync might have changed it based on journal entry lines - if payment.amount != original_amount: - import logging - _logger = logging.getLogger(__name__) - _logger.info(f"Restoring amount for payment {payment.id}: {payment.amount} -> {original_amount}") + # Restore gross amount from the move's counterpart lines + if payment.move_id: + liquidity_lines, counterpart_lines, writeoff_lines = payment._seek_for_lines() - # Use SQL to restore the original amount without triggering more syncs - payment.env.cr.execute( - "UPDATE account_payment SET amount = %s WHERE id = %s", - (original_amount, payment.id) - ) - payment.invalidate_recordset(['amount']) + # The gross amount is the sum of counterpart/writeoff lines that balance the liquidity line + # For outbound (Send Money): Gross = Sum of Debits (excluding liquidity) + # For inbound (Receive Money): Gross = Sum of Credits (excluding liquidity) + + non_liquidity_lines = counterpart_lines + writeoff_lines + if payment.payment_type == 'outbound': + gross_lines = non_liquidity_lines.filtered(lambda l: l.debit > 0) + else: + gross_lines = non_liquidity_lines.filtered(lambda l: l.credit > 0) + + if gross_lines: + # Use amount_currency because it represents the amount in payment currency + correct_gross_amount = sum(abs(l.amount_currency) for l in gross_lines) + + if abs(payment.amount - correct_gross_amount) > 0.001: + import logging + _logger = logging.getLogger(__name__) + _logger.info(f"Restoring gross amount for payment {payment.id}: {payment.amount} -> {correct_gross_amount}") + + # Use SQL to restore to avoid triggering more syncs + payment.env.cr.execute( + "UPDATE account_payment SET amount = %s WHERE id = %s", + (correct_gross_amount, payment.id) + ) + payment.invalidate_recordset(['amount']) # Ensure final_payment_amount is recalculated correctly payment._compute_final_payment_amount() diff --git a/models/payment_deduction_line.py b/models/payment_deduction_line.py index c0a5d68..8f82009 100755 --- a/models/payment_deduction_line.py +++ b/models/payment_deduction_line.py @@ -58,7 +58,7 @@ class PaymentDeductionLine(models.Model): 'account.account', string='Deduction Account', required=True, - domain="[('account_type', 'not in', ['asset_cash', 'asset_cash_bank', 'asset_receivable', 'liability_payable'])]", + domain="[('account_type', 'not in', ['asset_cash', 'asset_cash_bank', 'asset_receivable', 'liability_payable']), ('active', '=', True)]", help='Account where the deduction will be recorded (use tax payable or expense accounts, NOT payable/receivable accounts)', ) name = fields.Char( diff --git a/tests/test_account_payment.py b/tests/test_account_payment.py index fe9f297..44d8b03 100755 --- a/tests/test_account_payment.py +++ b/tests/test_account_payment.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- from odoo import fields -from odoo.tests import TransactionCase +from odoo.tests import TransactionCase, tagged from odoo.exceptions import ValidationError from hypothesis import given, strategies as st, settings +@tagged('post_install', '-at_install') class TestAccountPayment(TransactionCase): """Test cases for vendor payment deduction functionality""" @@ -37,7 +38,7 @@ class TestAccountPayment(TransactionCase): 'name': 'Withholding Tax Account', 'code': 'WHT001', 'account_type': 'expense', - 'company_id': self.env.company.id, + 'company_ids': [self.env.company.id], }) @given( diff --git a/tests/test_batch_payment_integration.py b/tests/test_batch_payment_integration.py index 82919e0..78baa82 100755 --- a/tests/test_batch_payment_integration.py +++ b/tests/test_batch_payment_integration.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- from odoo import fields -from odoo.tests import TransactionCase +from odoo.tests import TransactionCase, tagged from odoo.exceptions import ValidationError +@tagged('post_install', '-at_install') class TestBatchPaymentIntegration(TransactionCase): """Test cases for batch payment integration with deduction functionality""" diff --git a/wizard/payment_amount_fix_wizard.py b/wizard/payment_amount_fix_wizard.py index 769c75e..5083dc8 100755 --- a/wizard/payment_amount_fix_wizard.py +++ b/wizard/payment_amount_fix_wizard.py @@ -28,12 +28,17 @@ class PaymentAmountFixWizard(models.TransientModel): payments_to_fix = [] for payment in payments: if payment.move_id: - counterpart_lines = payment.move_id.line_ids.filtered( - lambda l: l.debit > 0 and l.account_id.account_type in ('liability_payable', 'expense') - ) - if counterpart_lines: - correct_amount = counterpart_lines[0].debit - if abs(payment.amount - correct_amount) > 0.01: + liquidity_lines, counterpart_lines, writeoff_lines = payment._seek_for_lines() + non_liquidity_lines = counterpart_lines + writeoff_lines + + if payment.payment_type == 'outbound': + gross_lines = non_liquidity_lines.filtered(lambda l: l.debit > 0) + else: + gross_lines = non_liquidity_lines.filtered(lambda l: l.credit > 0) + + if gross_lines: + correct_amount = sum(abs(l.amount_currency) for l in gross_lines) + if abs(payment.amount - correct_amount) > 0.001: payments_to_fix.append(payment.id) res['payment_ids'] = [(6, 0, payments_to_fix)] @@ -45,13 +50,17 @@ class PaymentAmountFixWizard(models.TransientModel): for payment in self.payment_ids: if payment.move_id: - counterpart_lines = payment.move_id.line_ids.filtered( - lambda l: l.debit > 0 and l.account_id.account_type in ('liability_payable', 'expense') - ) + liquidity_lines, counterpart_lines, writeoff_lines = payment._seek_for_lines() + non_liquidity_lines = counterpart_lines + writeoff_lines - if counterpart_lines: - correct_amount = counterpart_lines[0].debit - if abs(payment.amount - correct_amount) > 0.01: + if payment.payment_type == 'outbound': + gross_lines = non_liquidity_lines.filtered(lambda l: l.debit > 0) + else: + gross_lines = non_liquidity_lines.filtered(lambda l: l.credit > 0) + + if gross_lines: + correct_amount = sum(abs(l.amount_currency) for l in gross_lines) + if abs(payment.amount - correct_amount) > 0.001: # Fix using SQL to avoid sync issues payment.env.cr.execute( "UPDATE account_payment SET amount = %s WHERE id = %s",