fix the calculation for residual
This commit is contained in:
parent
9b5deb2f87
commit
0bdbede031
28
CHANGELOG.md
Normal file
28
CHANGELOG.md
Normal file
@ -0,0 +1,28 @@
|
||||
# Changelog
|
||||
|
||||
## [17.0.1.0.1] - 2025-11-21
|
||||
|
||||
### Fixed
|
||||
- **Bank Statement Matching Issue**: Fixed residual amounts showing incorrectly when payments are already reconciled/matched with bank statements
|
||||
- **Unmatched Payment Issue**: Fixed unmatched payments showing 0 residual when they should show actual residual amounts
|
||||
|
||||
### Changed
|
||||
- Modified `_compute_payment_residual()` to check `is_matched` status only
|
||||
- If payment is matched with bank statement (`is_matched=True`), residual is always 0
|
||||
- For unmatched payments, calculates residual from receivable/payable lines only
|
||||
- Excluded liquidity account lines (bank/cash accounts) from residual calculation
|
||||
- Removed `is_reconciled` check as it was causing false positives
|
||||
|
||||
### Technical Details
|
||||
The issues occurred because:
|
||||
1. When a payment is matched with a bank statement, the `is_matched` flag is set to True
|
||||
2. The previous implementation was calculating residuals regardless of matching status
|
||||
3. Even though the payment was matched, the receivable/payable line could still show a residual if not applied to an invoice
|
||||
4. Using `is_reconciled` check caused unmatched payments to show 0 residual incorrectly
|
||||
|
||||
The fix:
|
||||
- Only checks `is_matched` flag - if True, returns 0 residual
|
||||
- This ensures matched payments always show 0 residual
|
||||
- For unmatched payments, calculates residual from receivable/payable lines
|
||||
- Excludes liquidity account lines to focus on actual customer/vendor balances
|
||||
- This correctly shows residuals for unmatched payments while hiding them for matched ones
|
||||
19
README.md
19
README.md
@ -14,9 +14,10 @@ This module adds residual amount display to customer and vendor payments in Odoo
|
||||
The module extends the `account.payment` model with a computed field that:
|
||||
|
||||
1. Retrieves the journal entry lines created by the payment
|
||||
2. Identifies the counterpart lines (receivable/payable accounts) and write-off lines
|
||||
3. Sums up their `amount_residual` or `amount_residual_currency` values
|
||||
4. Displays the total unreconciled amount
|
||||
2. Identifies only the counterpart lines (receivable/payable accounts)
|
||||
3. Excludes liquidity account lines (bank/cash) to avoid false residuals when matched with bank statements
|
||||
4. Sums up their `amount_residual` or `amount_residual_currency` values based on currency
|
||||
5. Displays the total unreconciled amount
|
||||
|
||||
## Fields Added
|
||||
|
||||
@ -57,11 +58,13 @@ The module extends the `account.payment` model with a computed field that:
|
||||
- `account.view_account_payment_form`
|
||||
|
||||
### Computation Logic
|
||||
The residual is computed using the same approach as Odoo's built-in `is_reconciled` field:
|
||||
- Uses `_seek_for_lines()` to identify counterpart and write-off lines
|
||||
- Filters for reconcilable accounts
|
||||
- Sums the `amount_residual` or `amount_residual_currency` based on currency matching
|
||||
- This ensures consistency with Odoo's core reconciliation logic
|
||||
The residual is computed by:
|
||||
- Filtering only receivable/payable account lines (excludes liquidity accounts)
|
||||
- This prevents showing residuals when payments are matched with bank statements
|
||||
- Sums the `amount_residual` or `amount_residual_currency` based on payment currency
|
||||
- Uses company currency residual for same-currency payments
|
||||
- Uses foreign currency residual for multi-currency payments
|
||||
- This ensures accurate residual display regardless of bank statement matching
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
@ -1,62 +1 @@
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class AccountPayment(models.Model):
|
||||
_inherit = 'account.payment'
|
||||
|
||||
# Computed residual amount field
|
||||
payment_residual = fields.Monetary(
|
||||
string='Payment Residual',
|
||||
compute='_compute_payment_residual',
|
||||
currency_field='currency_id',
|
||||
help="Residual amount of this payment (amount not yet reconciled)",
|
||||
readonly=True
|
||||
)
|
||||
|
||||
# One2many to show journal items with residual amounts
|
||||
payment_move_line_ids = fields.One2many(
|
||||
'account.move.line',
|
||||
'payment_id',
|
||||
string='Payment Journal Items',
|
||||
domain=lambda self: [
|
||||
('account_id.reconcile', '=', True),
|
||||
('account_id.account_type', 'in', ['asset_receivable', 'liability_payable'])
|
||||
],
|
||||
readonly=True
|
||||
)
|
||||
|
||||
@api.depends('payment_move_line_ids.amount_residual',
|
||||
'payment_move_line_ids.account_id')
|
||||
def _compute_payment_residual(self):
|
||||
"""Compute the residual amount from payment journal items"""
|
||||
for payment in self:
|
||||
if payment.state in ['draft', 'cancel'] or not payment.payment_move_line_ids:
|
||||
payment.payment_residual = 0.0
|
||||
else:
|
||||
# Get all reconcilable journal items for this payment
|
||||
reconcilable_lines = payment.payment_move_line_ids.filtered(
|
||||
lambda l: l.account_id.reconcile and
|
||||
l.account_id.account_type in ['asset_receivable', 'liability_payable']
|
||||
)
|
||||
|
||||
# Sum up the residual amounts
|
||||
total_residual = sum(reconcilable_lines.mapped('amount_residual'))
|
||||
|
||||
# For display purposes, show absolute value with proper sign
|
||||
payment.payment_residual = total_residual
|
||||
|
||||
def action_view_journal_items(self):
|
||||
"""Action to view the payment's journal items"""
|
||||
self.ensure_one()
|
||||
return {
|
||||
'name': _('Payment Journal Items'),
|
||||
'view_mode': 'tree,form',
|
||||
'res_model': 'account.move.line',
|
||||
'domain': [('payment_id', '=', self.id)],
|
||||
'context': {
|
||||
'default_payment_id': self.id,
|
||||
'search_default_payment_id': self.id,
|
||||
},
|
||||
'type': 'ir.actions.act_window',
|
||||
}
|
||||
from . import account_payment
|
||||
@ -23,64 +23,48 @@ class AccountPayment(models.Model):
|
||||
|
||||
@api.depends('move_id.line_ids.amount_residual',
|
||||
'move_id.line_ids.amount_residual_currency',
|
||||
'move_id.line_ids.reconciled')
|
||||
'move_id.line_ids.reconciled',
|
||||
'is_matched')
|
||||
def _compute_payment_residual(self):
|
||||
"""Compute the residual amount from payment journal items
|
||||
|
||||
For testing: show all residual amounts regardless of reconciliation status
|
||||
Shows 0 residual when payment is matched with bank statement.
|
||||
Otherwise shows the residual from all reconcilable lines except liquidity accounts.
|
||||
"""
|
||||
for pay in self:
|
||||
# If payment is matched with bank statement, no residual to show
|
||||
if pay.is_matched:
|
||||
pay.payment_residual = 0.0
|
||||
pay.payment_residual_currency = 0.0
|
||||
continue
|
||||
|
||||
if not pay.move_id:
|
||||
pay.payment_residual = 0.0
|
||||
pay.payment_residual_currency = 0.0
|
||||
continue
|
||||
|
||||
# Get ALL move lines - let's see what's there
|
||||
all_lines = pay.move_id.line_ids
|
||||
# Get all reconcilable lines except liquidity accounts
|
||||
# This includes receivable, payable, and other reconcilable accounts
|
||||
# but excludes bank/cash accounts that get matched with statements
|
||||
reconcilable_lines = pay.move_id.line_ids.filtered(
|
||||
lambda line: line.account_id.reconcile and
|
||||
line.account_id.account_type not in ('asset_cash', 'liability_credit_card')
|
||||
)
|
||||
|
||||
# Sum ALL residual amounts to see what's happening
|
||||
total_residual = sum(all_lines.mapped('amount_residual'))
|
||||
total_residual_currency = sum(all_lines.mapped('amount_residual_currency'))
|
||||
# Calculate residual based on currency
|
||||
residual = 0.0
|
||||
residual_currency = 0.0
|
||||
|
||||
# For debugging - let's try multiple approaches:
|
||||
for line in reconcilable_lines:
|
||||
# Always add to company currency residual
|
||||
residual += line.amount_residual
|
||||
|
||||
# For foreign currency, use amount_residual_currency
|
||||
if line.currency_id and line.currency_id != pay.company_id.currency_id:
|
||||
residual_currency += line.amount_residual_currency
|
||||
else:
|
||||
residual_currency += line.amount_residual
|
||||
|
||||
# Approach 1: All lines residual
|
||||
#approach1_residual = total_residual
|
||||
#approach1_residual_currency = total_residual_currency
|
||||
|
||||
# Approach 2: Only reconcilable account lines
|
||||
reconcilable_lines = all_lines.filtered(lambda line: line.account_id.reconcile)
|
||||
approach2_residual = sum(reconcilable_lines.mapped('amount_residual'))
|
||||
approach2_residual_currency = sum(reconcilable_lines.mapped('amount_residual_currency'))
|
||||
|
||||
# Approach 3: Receivable/Payable account lines only
|
||||
rec_pay_lines = all_lines.filtered(lambda line: line.account_id.account_type in ('asset_receivable', 'liability_payable'))
|
||||
approach3_residual = sum(rec_pay_lines.mapped('amount_residual'))
|
||||
approach3_residual_currency = sum(rec_pay_lines.mapped('amount_residual_currency'))
|
||||
|
||||
# For now, let's use the approach that gives us the largest non-zero value
|
||||
# This will help us identify which approach works
|
||||
candidates = [
|
||||
#abs(approach1_residual),
|
||||
abs(approach2_residual),
|
||||
abs(approach3_residual),
|
||||
#abs(approach1_residual_currency),
|
||||
abs(approach2_residual_currency),
|
||||
abs(approach3_residual_currency)
|
||||
]
|
||||
|
||||
max_value = max(candidates) if candidates else 0.0
|
||||
|
||||
#if abs(approach1_residual) == max_value:
|
||||
# pay.payment_residual = abs(approach1_residual)
|
||||
# pay.payment_residual_currency = abs(approach1_residual_currency)
|
||||
if abs(approach2_residual) == max_value:
|
||||
pay.payment_residual = abs(approach2_residual)
|
||||
pay.payment_residual_currency = abs(approach2_residual_currency)
|
||||
elif abs(approach3_residual) == max_value:
|
||||
pay.payment_residual = abs(approach3_residual)
|
||||
pay.payment_residual_currency = abs(approach3_residual_currency)
|
||||
#else:
|
||||
# Fallback to currency residuals
|
||||
#pay.payment_residual = abs(approach1_residual_currency)
|
||||
#pay.payment_residual_currency = abs(approach1_residual_currency)
|
||||
# Store absolute values
|
||||
pay.payment_residual = abs(residual)
|
||||
pay.payment_residual_currency = abs(residual_currency)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user