188 lines
9.1 KiB
Python
188 lines
9.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
from odoo import models, fields, api, _
|
|
from odoo.exceptions import ValidationError
|
|
|
|
|
|
class AccountPayment(models.Model):
|
|
_inherit = 'account.payment'
|
|
|
|
# Flag to prevent infinite recursion during synchronization
|
|
_skip_amount_sync = False
|
|
|
|
amount_substract = fields.Monetary(
|
|
string='Amount Substract',
|
|
currency_field='currency_id',
|
|
help='Amount to be deducted from the payment (e.g., withholding tax, fees)',
|
|
readonly=False,
|
|
)
|
|
|
|
substract_account_id = fields.Many2one(
|
|
'account.account',
|
|
string='Substract Account',
|
|
domain="[('account_type', 'not in', ['asset_cash', 'asset_cash_bank']), ('deprecated', '=', False), ('company_id', '=', company_id)]",
|
|
help='Account where the deduction will be recorded',
|
|
readonly=False,
|
|
)
|
|
|
|
final_payment_amount = fields.Monetary(
|
|
string='Final Payment Amount',
|
|
currency_field='currency_id',
|
|
compute='_compute_final_payment_amount',
|
|
store=True,
|
|
help='Actual amount to be paid after deductions',
|
|
)
|
|
|
|
@api.depends('amount', 'amount_substract', 'currency_id')
|
|
def _compute_final_payment_amount(self):
|
|
for payment in self:
|
|
amount_substract = payment.amount_substract or 0.0
|
|
currency = payment.currency_id or payment.company_id.currency_id
|
|
payment.final_payment_amount = currency.round(payment.amount - amount_substract)
|
|
|
|
@api.constrains('amount', 'amount_substract')
|
|
def _check_amount_substract(self):
|
|
for payment in self:
|
|
if payment.amount_substract and payment.amount_substract < 0:
|
|
raise ValidationError(_("Amount Substract cannot be negative."))
|
|
if payment.amount_substract and payment.amount_substract > payment.amount:
|
|
raise ValidationError(_("Amount Substract cannot be greater than the payment amount."))
|
|
|
|
@api.constrains('amount_substract', 'substract_account_id')
|
|
def _check_substract_account(self):
|
|
for payment in self:
|
|
if payment.amount_substract > 0 and not payment.substract_account_id:
|
|
raise ValidationError(_("Please select a Substract Account when Amount Substract is specified."))
|
|
|
|
def _synchronize_from_moves(self, changed_fields):
|
|
"""
|
|
Override to prevent amount synchronization when we have a substract amount.
|
|
|
|
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).
|
|
|
|
Also handles the case where expense_account_id is used (from vendor_batch_payment_merge),
|
|
which replaces the payable account with an expense account.
|
|
"""
|
|
# Handle multiple records - process each payment individually
|
|
for payment in self:
|
|
# When expense_account_id is used with substract amount, the journal entry doesn't have
|
|
# a payable/receivable account. This causes Odoo's validation to fail.
|
|
# We need to skip the validation in this case.
|
|
if payment.expense_account_id and payment.amount_substract and payment.amount_substract > 0:
|
|
try:
|
|
result = super(AccountPayment, payment)._synchronize_from_moves(changed_fields)
|
|
except Exception as e:
|
|
# If validation fails due to missing payable/receivable account, it's expected
|
|
if 'receivable/payable account' in str(e):
|
|
# This is expected - just continue to next payment
|
|
continue
|
|
else:
|
|
# Re-raise other exceptions
|
|
raise
|
|
continue
|
|
|
|
# If we have a substract amount (but no expense_account_id), we need to handle the sync differently
|
|
if payment.amount_substract and payment.amount_substract > 0:
|
|
# Store the original amount before sync
|
|
original_amount = payment.amount
|
|
original_substract = payment.amount_substract
|
|
|
|
# Call parent sync
|
|
result = super(AccountPayment, payment)._synchronize_from_moves(changed_fields)
|
|
|
|
# Restore the original amount if it was changed by sync
|
|
if payment.amount != original_amount:
|
|
# Use write to update without triggering another sync
|
|
super(AccountPayment, payment).write({
|
|
'amount': original_amount,
|
|
'amount_substract': original_substract,
|
|
})
|
|
# Force recomputation of final_payment_amount
|
|
payment._compute_final_payment_amount()
|
|
else:
|
|
super(AccountPayment, payment)._synchronize_from_moves(changed_fields)
|
|
|
|
def _prepare_move_line_default_vals(self, write_off_line_vals=None, force_balance=None):
|
|
"""
|
|
Override to add substract account line when amount_substract > 0.
|
|
|
|
This method modifies the journal entry to:
|
|
1. Reduce the payable debit line to final_payment_amount
|
|
2. Add a new debit line for the substract account
|
|
3. Keep the bank credit line at the original amount
|
|
|
|
The resulting entry for outbound payment (amount=1000, substract=100):
|
|
- Payable: debit 900 (final_payment_amount)
|
|
- Substract: debit 100 (amount_substract)
|
|
- Bank: credit 1000 (original amount)
|
|
Total: debit 1000 = credit 1000 (balanced)
|
|
|
|
Requirements: 4.1, 4.2, 4.3, 4.4, 4.5
|
|
"""
|
|
# Get standard line values from parent
|
|
line_vals_list = super()._prepare_move_line_default_vals(write_off_line_vals, force_balance)
|
|
|
|
# Only modify if we have a deduction amount and account
|
|
if self.amount_substract and self.amount_substract > 0 and self.substract_account_id:
|
|
# For outbound payments, we need to:
|
|
# - Keep the payable debit (counterpart line) at the original amount
|
|
# - Add a credit line for the substract account (reduction)
|
|
# - Reduce the bank credit (liquidity line) to final_payment_amount
|
|
|
|
if self.payment_type == 'outbound':
|
|
# Check if substract line already exists (to prevent duplicates)
|
|
has_substract_line = any(
|
|
line.get('account_id') == self.substract_account_id.id
|
|
for line in line_vals_list
|
|
)
|
|
|
|
if not has_substract_line:
|
|
# The liquidity line is the first line (index 0) - this is the bank account
|
|
# The counterpart line is the second line (index 1) - this is the payable account
|
|
|
|
liquidity_line = line_vals_list[0]
|
|
|
|
# Convert amount_substract to company currency for the journal entry
|
|
substract_balance = self.currency_id._convert(
|
|
self.amount_substract,
|
|
self.company_id.currency_id,
|
|
self.company_id,
|
|
self.date,
|
|
)
|
|
|
|
# Don't adjust the liquidity (bank) line - keep it at the original amount
|
|
# The bank credit should be the original amount (requirement 4.4)
|
|
|
|
# Adjust the counterpart (payable) line - reduce the debit to final_payment_amount
|
|
# For outbound payment:
|
|
# - Original: amount_currency = amount, debit = amount
|
|
# - Modified: amount_currency = final_payment_amount, debit = final_payment_amount
|
|
counterpart_line = line_vals_list[1]
|
|
final_balance = self.currency_id._convert(
|
|
self.final_payment_amount,
|
|
self.company_id.currency_id,
|
|
self.company_id,
|
|
self.date,
|
|
)
|
|
counterpart_line['amount_currency'] = self.final_payment_amount
|
|
counterpart_line['debit'] = final_balance
|
|
|
|
# Create the substract account line (DEBIT - requirement 4.3)
|
|
substract_line_name = _('Payment Deduction: %s') % self.substract_account_id.name
|
|
substract_line = {
|
|
'name': substract_line_name,
|
|
'date_maturity': self.date,
|
|
'amount_currency': self.amount_substract, # Positive because it's a debit
|
|
'currency_id': self.currency_id.id,
|
|
'debit': substract_balance,
|
|
'credit': 0.0,
|
|
'partner_id': self.partner_id.id,
|
|
'account_id': self.substract_account_id.id,
|
|
}
|
|
|
|
# Add the substract line to the list
|
|
line_vals_list.append(substract_line)
|
|
|
|
return line_vals_list
|