[FIX] account: Fix payment terms computation + usability
- Compute the fixed amounts first before managing the percentage. Suppose a fixed line of 100 plus a percentage line of 100%. For 1000, the previous behavior was 100 + 1000 = 1100 != 1000 - Manage fixed amounts in multi-currency. With multi-currency, apply the rate to compute the company amount. - Ensure the full amount is dispatched exactly with percentages. Suppose 2 percent lines of 50%. To dispatch 0.03, the previous behavior was computing 0.02 + 0.02 = 0.04 != 0.03 leading to a not balanced journal entry. - Don't discard the last new line when clicking outside the sheet. Click on "add new line", then click on the empty space near the form view. => The last added line is removed. closes odoo/odoo#114025 Signed-off-by: Laurent Smet <las@odoo.com>
This commit is contained in:
committed by
Laurent Smet
parent
27fb535d9b
commit
01cd352a9a
@@ -138,7 +138,7 @@ class AccountPaymentTerm(models.Model):
|
||||
@api.constrains('line_ids')
|
||||
def _check_lines(self):
|
||||
for terms in self:
|
||||
if len(terms.line_ids.filtered(lambda r: r.value == 'percent')) > 0 and not terms._get_sum_line_ids():
|
||||
if sum(line.value_amount for line in terms.line_ids if line.value == 'percent') != 100:
|
||||
raise ValidationError(_('The Payment Term must have at least one percent line and the sum of the percent must be 100%.'))
|
||||
if len(terms.line_ids) > 1 and terms.early_discount:
|
||||
raise ValidationError(
|
||||
@@ -148,11 +148,6 @@ class AccountPaymentTerm(models.Model):
|
||||
if terms.early_discount and terms.discount_days <= 0:
|
||||
raise ValidationError(_("The Early Payment Discount days must be strictly positive."))
|
||||
|
||||
@api.model
|
||||
def _get_sum_line_ids(self):
|
||||
self.ensure_one()
|
||||
return sum(line.value_amount for line in self.line_ids if line.value == 'percent') == 100
|
||||
|
||||
def _compute_terms(self, date_ref, currency, company, tax_amount, tax_amount_currency, sign, untaxed_amount, untaxed_amount_currency):
|
||||
"""Get the distribution of this payment term.
|
||||
:param date_ref: The move date to take into account
|
||||
@@ -180,6 +175,7 @@ class AccountPaymentTerm(models.Model):
|
||||
}
|
||||
|
||||
if self.early_discount:
|
||||
# Early discount is only available on single line, 100% payment terms.
|
||||
discount_percentage = self.discount_percentage / 100.0
|
||||
if self.early_pay_discount_computation in ('excluded', 'mixed'):
|
||||
pay_term['discount_balance'] = company_currency.round(total_amount - untaxed_amount * discount_percentage)
|
||||
@@ -188,21 +184,38 @@ class AccountPaymentTerm(models.Model):
|
||||
pay_term['discount_balance'] = company_currency.round(total_amount * (1 - discount_percentage))
|
||||
pay_term['discount_amount_currency'] = currency.round(total_amount_currency * (1 - discount_percentage))
|
||||
|
||||
# Remove all fixed amounts.
|
||||
rate = abs(total_amount_currency / total_amount) if total_amount else 0.0
|
||||
for line in self.line_ids:
|
||||
if line.value == 'fixed':
|
||||
total_amount_currency -= line.value_amount
|
||||
total_amount -= currency.round(line.value_amount / rate) if rate else 0.0
|
||||
|
||||
residual_amount = total_amount
|
||||
residual_amount_currency = total_amount_currency
|
||||
|
||||
#We sort by the last line of a term
|
||||
term_lines = self.line_ids.sorted(lambda l: l == self.line_ids[-1])
|
||||
last_percent_line = term_lines.filtered(lambda l: l.value == 'percent')[-1]
|
||||
for line in self.line_ids.sorted(lambda l: l == self.line_ids[-1]):
|
||||
term_vals = {
|
||||
'date': line._get_due_date(date_ref),
|
||||
'company_amount': 0,
|
||||
'foreign_amount': 0,
|
||||
}
|
||||
|
||||
if line.value == 'fixed':
|
||||
term_vals['company_amount'] = sign * company_currency.round(line.value_amount)
|
||||
term_vals['company_amount'] = sign * company_currency.round(line.value_amount / rate) if rate else 0.0
|
||||
term_vals['foreign_amount'] = sign * currency.round(line.value_amount)
|
||||
elif line == last_percent_line:
|
||||
term_vals['company_amount'] = residual_amount
|
||||
term_vals['foreign_amount'] = residual_amount_currency
|
||||
else:
|
||||
term_vals['company_amount'] = company_currency.round(total_amount * (line.value_amount / 100.0))
|
||||
term_vals['foreign_amount'] = currency.round(total_amount_currency * (line.value_amount / 100.0))
|
||||
|
||||
line_amount = company_currency.round(total_amount * (line.value_amount / 100.0))
|
||||
line_amount_currency = currency.round(total_amount_currency * (line.value_amount / 100.0))
|
||||
term_vals['company_amount'] = line_amount
|
||||
term_vals['foreign_amount'] = line_amount_currency
|
||||
residual_amount -= line_amount
|
||||
residual_amount_currency -= line_amount_currency
|
||||
pay_term["line_ids"].append(term_vals)
|
||||
|
||||
return pay_term
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
import { ListRenderer } from "@web/views/list/list_renderer";
|
||||
import { X2ManyField, x2ManyField } from "@web/views/fields/x2many/x2many_field";
|
||||
|
||||
export class PaymentTermLineIdsRenderer extends ListRenderer {
|
||||
|
||||
/* override */
|
||||
onGlobalClick(ev) {
|
||||
// Prevent the discard of new records when clicking outside of the sheet.
|
||||
// This is needed because the user is not forced to edit something on the newly
|
||||
// created record. Therefore, there is no reason to remove this record when he
|
||||
// attempt to save the form.
|
||||
this.props.list.editedRecord = null;
|
||||
super.onGlobalClick(ev);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class PaymentTermLineIdsOne2Many extends X2ManyField {
|
||||
static components = {...X2ManyField.components, ListRenderer: PaymentTermLineIdsRenderer}
|
||||
}
|
||||
|
||||
export const PaymentTermLineIds = {
|
||||
...x2ManyField,
|
||||
component: PaymentTermLineIdsOne2Many,
|
||||
}
|
||||
|
||||
registry.category("fields").add("payment_term_line_ids", PaymentTermLineIds);
|
||||
@@ -205,3 +205,66 @@ class TestAccountPaymentTerms(AccountTestInvoicingCommon):
|
||||
}],
|
||||
},
|
||||
)
|
||||
|
||||
def test_payment_term_residual_amount_on_last_line_with_fixed_amount_multi_currency(self):
|
||||
pay_term = self.env['account.payment.term'].create({
|
||||
'name': "test_payment_term_residual_amount_on_last_line",
|
||||
'line_ids': [
|
||||
Command.create({
|
||||
'value_amount': 50,
|
||||
'value': 'percent',
|
||||
'nb_days': 0,
|
||||
}),
|
||||
Command.create({
|
||||
'value_amount': 50,
|
||||
'value': 'percent',
|
||||
'nb_days': 0,
|
||||
}),
|
||||
Command.create({
|
||||
'value_amount': 0.02,
|
||||
'value': 'fixed',
|
||||
'nb_days': 0,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
computed_term = pay_term._compute_terms(
|
||||
fields.Date.from_string('2016-01-01'), self.currency_data['currency'], self.env.company,
|
||||
0.0, 0.0, 1.0, 0.04, 0.09,
|
||||
)
|
||||
self.assertEqual(
|
||||
[
|
||||
(
|
||||
self.currency_data['currency'].round(l['foreign_amount']),
|
||||
self.company_data['currency'].round(l['company_amount']),
|
||||
)
|
||||
for l in computed_term['line_ids']
|
||||
],
|
||||
[(0.035, 0.02), (0.035, 0.01), (0.02, 0.01)],
|
||||
)
|
||||
|
||||
def test_payment_term_residual_amount_on_last_line(self):
|
||||
pay_term = self.env['account.payment.term'].create({
|
||||
'name': "test_payment_term_residual_amount_on_last_line",
|
||||
'line_ids': [
|
||||
Command.create({
|
||||
'value_amount': 50,
|
||||
'value': 'percent',
|
||||
'nb_days': 0,
|
||||
}),
|
||||
Command.create({
|
||||
'value_amount': 50,
|
||||
'value': 'percent',
|
||||
'nb_days': 0,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
computed_term = pay_term._compute_terms(
|
||||
fields.Date.from_string('2016-01-01'), self.env.company.currency_id, self.env.company,
|
||||
0.0, 0.0, 1.0, 0.03, 0.03,
|
||||
)
|
||||
self.assertEqual(
|
||||
[self.env.company.currency_id.round(l['foreign_amount']) for l in computed_term['line_ids']],
|
||||
[0.02, 0.01],
|
||||
)
|
||||
|
||||
@@ -62,9 +62,9 @@
|
||||
</group>
|
||||
<group>
|
||||
<group string="Due Terms">
|
||||
<field name="line_ids" nolabel="1" colspan="2">
|
||||
<field name="line_ids" widget="payment_term_line_ids" nolabel="1" colspan="2">
|
||||
<tree string="Payment Terms" editable="bottom" no_open="True">
|
||||
<field name="value_amount" attrs="{'invisible': [('value', '=', 'balance')]}"/>
|
||||
<field name="value_amount"/>
|
||||
<field name="value" nolabel="1"/>
|
||||
<field name="nb_days" string="After"/>
|
||||
<field name="delay_type" nolabel="1"/>
|
||||
|
||||
Reference in New Issue
Block a user