mirror of
https://github.com/bringout/oca-ocb-accounting.git
synced 2026-04-23 10:22:04 +02:00
19.0 vanilla
This commit is contained in:
parent
ba20ce7443
commit
768b70e05e
2357 changed files with 1057103 additions and 712486 deletions
|
|
@ -2,51 +2,116 @@
|
|||
|
||||
from odoo import api, fields, models, _, Command
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.tools import format_date, formatLang, frozendict
|
||||
from odoo.tools import format_date, formatLang, frozendict, date_utils
|
||||
from odoo.tools.float_utils import float_round
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
|
||||
class AccountPaymentTerm(models.Model):
|
||||
_name = "account.payment.term"
|
||||
_name = 'account.payment.term'
|
||||
_description = "Payment Terms"
|
||||
_order = "sequence, id"
|
||||
_check_company_domain = models.check_company_domain_parent_of
|
||||
|
||||
def _default_line_ids(self):
|
||||
return [Command.create({'value': 'balance', 'value_amount': 0.0, 'days': 0, 'end_month': False})]
|
||||
|
||||
def _default_example_amount(self):
|
||||
return self._context.get('example_amount') or 100 # Force default value if the context is set to False
|
||||
return [Command.create({'value': 'percent', 'value_amount': 100.0, 'nb_days': 0})]
|
||||
|
||||
def _default_example_date(self):
|
||||
return self._context.get('example_date') or fields.Date.today()
|
||||
return self.env.context.get('example_date') or fields.Date.today()
|
||||
|
||||
name = fields.Char(string='Payment Terms', translate=True, required=True)
|
||||
active = fields.Boolean(default=True, help="If the active field is set to False, it will allow you to hide the payment terms without removing it.")
|
||||
note = fields.Html(string='Description on the Invoice', translate=True)
|
||||
line_ids = fields.One2many('account.payment.term.line', 'payment_id', string='Terms', copy=True, default=_default_line_ids)
|
||||
company_id = fields.Many2one('res.company', string='Company')
|
||||
fiscal_country_codes = fields.Char(compute='_compute_fiscal_country_codes')
|
||||
sequence = fields.Integer(required=True, default=10)
|
||||
display_on_invoice = fields.Boolean(string='Display terms on invoice', help="If set, the payment deadlines and respective due amounts will be detailed on invoices.")
|
||||
example_amount = fields.Float(default=_default_example_amount, store=False)
|
||||
currency_id = fields.Many2one('res.currency', compute="_compute_currency_id")
|
||||
|
||||
display_on_invoice = fields.Boolean(string='Show installment dates', default=True)
|
||||
example_amount = fields.Monetary(currency_field='currency_id', default=1000, store=False, readonly=True)
|
||||
example_date = fields.Date(string='Date example', default=_default_example_date, store=False)
|
||||
example_invalid = fields.Boolean(compute='_compute_example_invalid')
|
||||
example_preview = fields.Html(compute='_compute_example_preview')
|
||||
example_preview_discount = fields.Html(compute='_compute_example_preview')
|
||||
|
||||
discount_percentage = fields.Float(string='Discount %', help='Early Payment Discount granted for this payment term', default=2.0)
|
||||
discount_days = fields.Integer(string='Discount Days', help='Number of days before the early payment proposition expires', default=10)
|
||||
early_pay_discount_computation = fields.Selection([
|
||||
('included', 'On early payment'),
|
||||
('excluded', 'Never'),
|
||||
('mixed', 'Always (upon invoice)'),
|
||||
], string='Cash Discount Tax Reduction', readonly=False, store=True, compute='_compute_discount_computation')
|
||||
early_discount = fields.Boolean(string='Early Discount')
|
||||
|
||||
@api.depends('company_id')
|
||||
@api.depends_context('allowed_company_ids')
|
||||
def _compute_fiscal_country_codes(self):
|
||||
for record in self:
|
||||
allowed_companies = record.company_id or self.env.companies
|
||||
record.fiscal_country_codes = ",".join(allowed_companies.mapped('account_fiscal_country_id.code'))
|
||||
|
||||
@api.depends_context('company')
|
||||
@api.depends('company_id')
|
||||
def _compute_currency_id(self):
|
||||
for payment_term in self:
|
||||
payment_term.currency_id = payment_term.company_id.currency_id or self.env.company.currency_id
|
||||
|
||||
def _get_amount_due_after_discount(self, total_amount, untaxed_amount):
|
||||
self.ensure_one()
|
||||
if self.early_discount:
|
||||
percentage = self.discount_percentage / 100.0
|
||||
if self.early_pay_discount_computation in ('excluded', 'mixed'):
|
||||
discount_amount_currency = (total_amount - untaxed_amount) * percentage
|
||||
else:
|
||||
discount_amount_currency = total_amount * percentage
|
||||
amount_due = self.currency_id.round(total_amount - discount_amount_currency)
|
||||
if self.env.context.get('active_model') == 'account.move' and (active_id := self.env.context.get('active_id')):
|
||||
move = self.env['account.move'].browse(active_id)
|
||||
cash_rounding = move.invoice_cash_rounding_id
|
||||
currency = move.currency_id
|
||||
if cash_rounding:
|
||||
cash_rounding_difference = cash_rounding.compute_difference(currency, amount_due)
|
||||
if not currency.is_zero(cash_rounding_difference):
|
||||
amount_due = self.currency_id.round(amount_due + cash_rounding_difference)
|
||||
return amount_due
|
||||
return total_amount
|
||||
|
||||
@api.depends('company_id')
|
||||
def _compute_discount_computation(self):
|
||||
for pay_term in self:
|
||||
country_code = pay_term.company_id.country_code or self.env.company.country_code
|
||||
if country_code == 'BE':
|
||||
pay_term.early_pay_discount_computation = 'mixed'
|
||||
elif country_code == 'NL':
|
||||
pay_term.early_pay_discount_computation = 'excluded'
|
||||
else:
|
||||
pay_term.early_pay_discount_computation = 'included'
|
||||
|
||||
@api.depends('line_ids')
|
||||
def _compute_example_invalid(self):
|
||||
for payment_term in self:
|
||||
payment_term.example_invalid = len(payment_term.line_ids.filtered(lambda l: l.value == 'balance')) != 1
|
||||
payment_term.example_invalid = not payment_term.line_ids
|
||||
|
||||
@api.depends('example_amount', 'example_date', 'line_ids.value', 'line_ids.value_amount',
|
||||
'line_ids.months', 'line_ids.days', 'line_ids.end_month', 'line_ids.days_after')
|
||||
@api.depends('currency_id', 'example_amount', 'example_date', 'line_ids.value', 'line_ids.value_amount', 'line_ids.nb_days', 'early_discount', 'discount_percentage', 'discount_days')
|
||||
def _compute_example_preview(self):
|
||||
for record in self:
|
||||
example_preview = ""
|
||||
record.example_preview_discount = ""
|
||||
currency = record.currency_id
|
||||
if record.early_discount:
|
||||
date = record._get_last_discount_date_formatted(record.example_date or fields.Date.context_today(record))
|
||||
discount_amount = record._get_amount_due_after_discount(record.example_amount, 0.0)
|
||||
record.example_preview_discount = _(
|
||||
"Early Payment Discount: <b>%(amount)s</b> if paid before <b>%(date)s</b>",
|
||||
amount=formatLang(self.env, discount_amount, currency_obj=currency),
|
||||
date=date,
|
||||
)
|
||||
|
||||
if not record.example_invalid:
|
||||
currency = self.env.company.currency_id
|
||||
terms = record._compute_terms(
|
||||
date_ref=record.example_date,
|
||||
date_ref=record.example_date or fields.Date.context_today(record),
|
||||
currency=currency,
|
||||
company=self.env.company,
|
||||
tax_amount=0,
|
||||
|
|
@ -54,60 +119,54 @@ class AccountPaymentTerm(models.Model):
|
|||
untaxed_amount=record.example_amount,
|
||||
untaxed_amount_currency=record.example_amount,
|
||||
sign=1)
|
||||
for i, info_by_dates in enumerate(record._get_amount_by_date(terms, currency).values()):
|
||||
for i, info_by_dates in enumerate(record._get_amount_by_date(terms).values()):
|
||||
date = info_by_dates['date']
|
||||
discount_date = info_by_dates['discount_date']
|
||||
amount = info_by_dates['amount']
|
||||
discount_amount = info_by_dates['discounted_amount'] or 0.0
|
||||
example_preview += "<div style='margin-left: 20px;'>"
|
||||
example_preview += "<div>"
|
||||
example_preview += _(
|
||||
"<b>%(count)s#</b> Installment of <b>%(amount)s</b> on <b style='color: #704A66;'>%(date)s</b>",
|
||||
"<b>%(count)s#</b> Installment of <b>%(amount)s</b> due on <b style='color: #704A66;'>%(date)s</b>",
|
||||
count=i+1,
|
||||
amount=formatLang(self.env, amount, monetary=True, currency_obj=currency),
|
||||
amount=formatLang(self.env, amount, currency_obj=currency),
|
||||
date=date,
|
||||
)
|
||||
if discount_date:
|
||||
example_preview += _(
|
||||
" (<b>%(amount)s</b> if paid before <b>%(date)s</b>)",
|
||||
amount=formatLang(self.env, discount_amount, monetary=True, currency_obj=currency),
|
||||
date=format_date(self.env, terms[i].get('discount_date')),
|
||||
)
|
||||
example_preview += "</div>"
|
||||
|
||||
record.example_preview = example_preview
|
||||
|
||||
@api.model
|
||||
def _get_amount_by_date(self, terms, currency):
|
||||
def _get_amount_by_date(self, terms):
|
||||
"""
|
||||
Returns a dictionary with the amount for each date of the payment term
|
||||
(grouped by date, discounted percentage and discount last date,
|
||||
sorted by date and ignoring null amounts).
|
||||
"""
|
||||
terms = sorted(terms, key=lambda t: t.get('date'))
|
||||
terms_lines = sorted(terms["line_ids"], key=lambda t: t.get('date'))
|
||||
amount_by_date = {}
|
||||
for term in terms:
|
||||
for term in terms_lines:
|
||||
key = frozendict({
|
||||
'date': term['date'],
|
||||
'discount_date': term['discount_date'],
|
||||
'discount_percentage': term['discount_percentage'],
|
||||
})
|
||||
results = amount_by_date.setdefault(key, {
|
||||
'date': format_date(self.env, term['date']),
|
||||
'amount': 0.0,
|
||||
'discounted_amount': 0.0,
|
||||
'discount_date': format_date(self.env, term['discount_date']),
|
||||
})
|
||||
results['amount'] += term['foreign_amount']
|
||||
results['discounted_amount'] += term['discount_amount_currency']
|
||||
return amount_by_date
|
||||
|
||||
@api.constrains('line_ids')
|
||||
@api.constrains('line_ids', 'early_discount')
|
||||
def _check_lines(self):
|
||||
round_precision = self.env['decimal.precision'].precision_get('Payment Terms')
|
||||
for terms in self:
|
||||
if len(terms.line_ids.filtered(lambda r: r.value == 'balance')) != 1:
|
||||
raise ValidationError(_('The Payment Term must have one Balance line.'))
|
||||
if terms.line_ids.filtered(lambda r: r.value == 'fixed' and r.discount_percentage):
|
||||
raise ValidationError(_("You can't mix fixed amount with early payment percentage"))
|
||||
total_percent = sum(line.value_amount for line in terms.line_ids if line.value == 'percent')
|
||||
if float_round(total_percent, precision_digits=round_precision) != 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(
|
||||
_("The Early Payment Discount functionality can only be used with payment terms using a single 100% line. "))
|
||||
if terms.early_discount and terms.discount_percentage <= 0.0:
|
||||
raise ValidationError(_("The Early Payment Discount must be strictly positive."))
|
||||
if terms.early_discount and terms.discount_days <= 0:
|
||||
raise ValidationError(_("The Early Payment Discount days must be strictly positive."))
|
||||
|
||||
def _compute_terms(self, date_ref, currency, company, tax_amount, tax_amount_currency, sign, untaxed_amount, untaxed_amount_currency, cash_rounding=None):
|
||||
"""Get the distribution of this payment term.
|
||||
|
|
@ -123,162 +182,186 @@ class AccountPaymentTerm(models.Model):
|
|||
We assume that the input total in move currency (tax_amount_currency + untaxed_amount_currency) is already cash rounded.
|
||||
The cash rounding does not change the totals: Consider the sum of all the computed payment term amounts in move / company currency.
|
||||
It is the same as the input total in move / company currency.
|
||||
:return (list<tuple<datetime.date,tuple<float,float>>>): the amount in the company's currency and
|
||||
the document's currency, respectively for each required payment date
|
||||
"""
|
||||
self.ensure_one()
|
||||
company_currency = company.currency_id
|
||||
tax_amount_left = tax_amount
|
||||
tax_amount_currency_left = tax_amount_currency
|
||||
untaxed_amount_left = untaxed_amount
|
||||
untaxed_amount_currency_left = untaxed_amount_currency
|
||||
total_amount = tax_amount + untaxed_amount
|
||||
total_amount_currency = tax_amount_currency + untaxed_amount_currency
|
||||
foreign_rounding_amount = 0
|
||||
company_rounding_amount = 0
|
||||
result = []
|
||||
rate = abs(total_amount_currency / total_amount) if total_amount else 0.0
|
||||
|
||||
for line in self.line_ids.sorted(lambda line: line.value == 'balance'):
|
||||
pay_term = {
|
||||
'total_amount': total_amount,
|
||||
'discount_percentage': self.discount_percentage if self.early_discount else 0.0,
|
||||
'discount_date': date_ref + relativedelta(days=(self.discount_days or 0)) if self.early_discount else False,
|
||||
'discount_balance': 0,
|
||||
'line_ids': [],
|
||||
}
|
||||
|
||||
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)
|
||||
pay_term['discount_amount_currency'] = currency.round(total_amount_currency - untaxed_amount_currency * discount_percentage)
|
||||
else:
|
||||
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))
|
||||
|
||||
if cash_rounding:
|
||||
cash_rounding_difference_currency = cash_rounding.compute_difference(currency, pay_term['discount_amount_currency'])
|
||||
if not currency.is_zero(cash_rounding_difference_currency):
|
||||
pay_term['discount_amount_currency'] += cash_rounding_difference_currency
|
||||
pay_term['discount_balance'] = company_currency.round(pay_term['discount_amount_currency'] / rate) if rate else 0.0
|
||||
|
||||
residual_amount = total_amount
|
||||
residual_amount_currency = total_amount_currency
|
||||
|
||||
for i, line in enumerate(self.line_ids):
|
||||
term_vals = {
|
||||
'date': line._get_due_date(date_ref),
|
||||
'has_discount': line.discount_percentage,
|
||||
'discount_date': None,
|
||||
'discount_amount_currency': 0.0,
|
||||
'discount_balance': 0.0,
|
||||
'discount_percentage': line.discount_percentage,
|
||||
'company_amount': 0,
|
||||
'foreign_amount': 0,
|
||||
}
|
||||
|
||||
if line.value == 'fixed':
|
||||
term_vals['company_amount'] = sign * company_currency.round(line.value_amount)
|
||||
# The last line is always the balance, no matter the type
|
||||
on_balance_line = i == len(self.line_ids) - 1
|
||||
if on_balance_line:
|
||||
term_vals['company_amount'] = residual_amount
|
||||
term_vals['foreign_amount'] = residual_amount_currency
|
||||
elif line.value == 'fixed':
|
||||
# Fixed amounts
|
||||
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)
|
||||
company_proportion = tax_amount/untaxed_amount if untaxed_amount else 1
|
||||
foreign_proportion = tax_amount_currency/untaxed_amount_currency if untaxed_amount_currency else 1
|
||||
line_tax_amount = company_currency.round(line.value_amount * company_proportion) * sign
|
||||
line_tax_amount_currency = currency.round(line.value_amount * foreign_proportion) * sign
|
||||
line_untaxed_amount = term_vals['company_amount'] - line_tax_amount
|
||||
line_untaxed_amount_currency = term_vals['foreign_amount'] - line_tax_amount_currency
|
||||
elif line.value == 'percent':
|
||||
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_tax_amount = company_currency.round(tax_amount * (line.value_amount / 100.0))
|
||||
line_tax_amount_currency = currency.round(tax_amount_currency * (line.value_amount / 100.0))
|
||||
line_untaxed_amount = term_vals['company_amount'] - line_tax_amount
|
||||
line_untaxed_amount_currency = term_vals['foreign_amount'] - line_tax_amount_currency
|
||||
else:
|
||||
line_tax_amount = line_tax_amount_currency = line_untaxed_amount = line_untaxed_amount_currency = 0.0
|
||||
# Percentage amounts
|
||||
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
|
||||
|
||||
# The following values do not account for any potential cash rounding
|
||||
tax_amount_left -= line_tax_amount
|
||||
tax_amount_currency_left -= line_tax_amount_currency
|
||||
untaxed_amount_left -= line_untaxed_amount
|
||||
untaxed_amount_currency_left -= line_untaxed_amount_currency
|
||||
|
||||
if cash_rounding and line.value in ['fixed', 'percent']:
|
||||
if cash_rounding and not on_balance_line:
|
||||
# The value `residual_amount_currency` is always cash rounded (in case of cash rounding).
|
||||
# * We assume `total_amount_currency` is cash rounded.
|
||||
# * We only subtract cash rounded amounts.
|
||||
# Thus the balance line is cash rounded.
|
||||
cash_rounding_difference_currency = cash_rounding.compute_difference(currency, term_vals['foreign_amount'])
|
||||
if not currency.is_zero(cash_rounding_difference_currency):
|
||||
rate = abs(term_vals['foreign_amount'] / term_vals['company_amount']) if term_vals['company_amount'] else 1.0
|
||||
|
||||
foreign_rounding_amount += cash_rounding_difference_currency
|
||||
term_vals['foreign_amount'] += cash_rounding_difference_currency
|
||||
term_vals['company_amount'] = company_currency.round(term_vals['foreign_amount'] / rate) if rate else 0.0
|
||||
|
||||
company_amount = company_currency.round(term_vals['foreign_amount'] / rate)
|
||||
cash_rounding_difference = company_amount - term_vals['company_amount']
|
||||
if not currency.is_zero(cash_rounding_difference):
|
||||
company_rounding_amount += cash_rounding_difference
|
||||
term_vals['company_amount'] = company_amount
|
||||
residual_amount -= term_vals['company_amount']
|
||||
residual_amount_currency -= term_vals['foreign_amount']
|
||||
pay_term['line_ids'].append(term_vals)
|
||||
|
||||
if line.value == 'balance':
|
||||
# The `*_amount_left` variables do not account for cash rounding.
|
||||
# Here we remove the total amount added by the cash rounding from the amount left.
|
||||
# This way the totals in company and move currency remain unchanged (compared to the input).
|
||||
# We assume the foreign total (`tax_amount_currency + untaxed_amount_currency`) is cash rounded.
|
||||
# The following right side is the same as subtracting all the (cash rounded) foreign payment term amounts from the foreign total.
|
||||
# Thus it is the remaining foreign amount and also cash rounded.
|
||||
term_vals['foreign_amount'] = tax_amount_currency_left + untaxed_amount_currency_left - foreign_rounding_amount
|
||||
term_vals['company_amount'] = tax_amount_left + untaxed_amount_left - company_rounding_amount
|
||||
|
||||
line_tax_amount = tax_amount_left
|
||||
line_tax_amount_currency = tax_amount_currency_left
|
||||
line_untaxed_amount = untaxed_amount_left
|
||||
line_untaxed_amount_currency = untaxed_amount_currency_left
|
||||
|
||||
if line.discount_percentage:
|
||||
if company.early_pay_discount_computation in ('excluded', 'mixed'):
|
||||
term_vals['discount_balance'] = company_currency.round(term_vals['company_amount'] - line_untaxed_amount * line.discount_percentage / 100.0)
|
||||
term_vals['discount_amount_currency'] = currency.round(term_vals['foreign_amount'] - line_untaxed_amount_currency * line.discount_percentage / 100.0)
|
||||
else:
|
||||
term_vals['discount_balance'] = company_currency.round(term_vals['company_amount'] * (1 - (line.discount_percentage / 100.0)))
|
||||
term_vals['discount_amount_currency'] = currency.round(term_vals['foreign_amount'] * (1 - (line.discount_percentage / 100.0)))
|
||||
term_vals['discount_date'] = date_ref + relativedelta(days=line.discount_days)
|
||||
|
||||
if cash_rounding and line.discount_percentage:
|
||||
cash_rounding_difference_currency = cash_rounding.compute_difference(currency, term_vals['discount_amount_currency'])
|
||||
if not currency.is_zero(cash_rounding_difference_currency):
|
||||
rate = abs(term_vals['discount_amount_currency'] / term_vals['discount_balance']) if term_vals['discount_balance'] else 1.0
|
||||
term_vals['discount_amount_currency'] += cash_rounding_difference_currency
|
||||
term_vals['discount_balance'] = company_currency.round(term_vals['discount_amount_currency'] / rate)
|
||||
|
||||
result.append(term_vals)
|
||||
return result
|
||||
return pay_term
|
||||
|
||||
@api.ondelete(at_uninstall=False)
|
||||
def _unlink_except_referenced_terms(self):
|
||||
if self.env['account.move'].search([('invoice_payment_term_id', 'in', self.ids)]):
|
||||
raise UserError(_('You can not delete payment terms as other records still reference it. However, you can archive it.'))
|
||||
if self.env['account.move'].search_count([('invoice_payment_term_id', 'in', self.ids)], limit=1):
|
||||
raise UserError(_("Uh-oh! Those payment terms are quite popular and can't be deleted since there are still some records referencing them. How about archiving them instead?"))
|
||||
|
||||
def unlink(self):
|
||||
for terms in self:
|
||||
self.env['ir.property'].sudo().search(
|
||||
[('value_reference', 'in', ['account.payment.term,%s'%payment_term.id for payment_term in terms])]
|
||||
).unlink()
|
||||
return super(AccountPaymentTerm, self).unlink()
|
||||
def _get_last_discount_date(self, date_ref):
|
||||
self.ensure_one()
|
||||
if not date_ref:
|
||||
return None
|
||||
return date_ref + relativedelta(days=self.discount_days or 0) if self.early_discount else False
|
||||
|
||||
def copy(self, default=None):
|
||||
def _get_last_discount_date_formatted(self, date_ref):
|
||||
self.ensure_one()
|
||||
if not date_ref:
|
||||
return None
|
||||
return format_date(self.env, self._get_last_discount_date(date_ref))
|
||||
|
||||
def copy_data(self, default=None):
|
||||
default = dict(default or {})
|
||||
default['name'] = _('%s (copy)', self.name)
|
||||
return super().copy(default)
|
||||
vals_list = super().copy_data(default=default)
|
||||
return [dict(vals, name=_("%s (copy)", line.name)) for line, vals in zip(self, vals_list)]
|
||||
|
||||
|
||||
class AccountPaymentTermLine(models.Model):
|
||||
_name = "account.payment.term.line"
|
||||
_name = 'account.payment.term.line'
|
||||
_description = "Payment Terms Line"
|
||||
_order = "id"
|
||||
|
||||
value = fields.Selection([
|
||||
('balance', 'Balance'),
|
||||
('percent', 'Percent'),
|
||||
('fixed', 'Fixed Amount')
|
||||
], string='Type', required=True, default='percent',
|
||||
('fixed', 'Fixed')
|
||||
], required=True, default='percent',
|
||||
help="Select here the kind of valuation related to this payment terms line.")
|
||||
value_amount = fields.Float(string='Value', digits='Payment Terms', help="For percent enter a ratio between 0-100.")
|
||||
months = fields.Integer(string='Months', required=True, default=0)
|
||||
days = fields.Integer(string='Days', required=True, default=0)
|
||||
end_month = fields.Boolean(string='End of month', help="Switch to end of the month after having added months or days")
|
||||
days_after = fields.Integer(string='Days after End of month', help="Days to add after the end of the month")
|
||||
discount_percentage = fields.Float(string='Discount %', help='Early Payment Discount granted for this line')
|
||||
discount_days = fields.Integer(string='Discount Days', help='Number of days before the early payment proposition expires')
|
||||
value_amount = fields.Float(string='Due', digits='Payment Terms',
|
||||
help="For percent enter a ratio between 0-100.",
|
||||
compute='_compute_value_amount', store=True, readonly=False)
|
||||
delay_type = fields.Selection([
|
||||
('days_after', 'Days after invoice date'),
|
||||
('days_after_end_of_month', 'Days after end of month'),
|
||||
('days_after_end_of_next_month', 'Days after end of next month'),
|
||||
('days_end_of_month_on_the', 'Days end of month on the'),
|
||||
], required=True, default='days_after')
|
||||
display_days_next_month = fields.Boolean(compute='_compute_display_days_next_month')
|
||||
days_next_month = fields.Char(
|
||||
string='Days on the next month',
|
||||
readonly=False,
|
||||
default='10',
|
||||
size=2,
|
||||
)
|
||||
nb_days = fields.Integer(string='Days', readonly=False, store=True, compute='_compute_days')
|
||||
payment_id = fields.Many2one('account.payment.term', string='Payment Terms', required=True, index=True, ondelete='cascade')
|
||||
|
||||
def _get_due_date(self, date_ref):
|
||||
self.ensure_one()
|
||||
due_date = fields.Date.from_string(date_ref) or fields.Date.today()
|
||||
due_date += relativedelta(months=self.months)
|
||||
due_date += relativedelta(days=self.days)
|
||||
if self.end_month:
|
||||
due_date += relativedelta(day=31)
|
||||
due_date += relativedelta(days=self.days_after)
|
||||
return due_date
|
||||
if self.delay_type == 'days_after_end_of_month':
|
||||
return date_utils.end_of(due_date, 'month') + relativedelta(days=self.nb_days)
|
||||
elif self.delay_type == 'days_after_end_of_next_month':
|
||||
return date_utils.end_of(due_date + relativedelta(months=1), 'month') + relativedelta(days=self.nb_days)
|
||||
elif self.delay_type == 'days_end_of_month_on_the':
|
||||
try:
|
||||
days_next_month = int(self.days_next_month)
|
||||
except ValueError:
|
||||
days_next_month = 1
|
||||
|
||||
@api.constrains('value', 'value_amount', 'discount_percentage')
|
||||
if not days_next_month:
|
||||
return date_utils.end_of(due_date + relativedelta(days=self.nb_days), 'month')
|
||||
|
||||
return due_date + relativedelta(days=self.nb_days) + relativedelta(months=1, day=days_next_month)
|
||||
return due_date + relativedelta(days=self.nb_days)
|
||||
|
||||
@api.constrains('days_next_month')
|
||||
def _check_valid_char_value(self):
|
||||
for record in self:
|
||||
if record.days_next_month and record.days_next_month.isnumeric():
|
||||
if not (0 <= int(record.days_next_month) <= 31):
|
||||
raise ValidationError(_('The days added must be between 0 and 31.'))
|
||||
else:
|
||||
raise ValidationError(_('The days added must be a number and has to be between 0 and 31.'))
|
||||
|
||||
@api.depends('delay_type')
|
||||
def _compute_display_days_next_month(self):
|
||||
for record in self:
|
||||
record.display_days_next_month = record.delay_type == 'days_end_of_month_on_the'
|
||||
|
||||
@api.constrains('value', 'value_amount')
|
||||
def _check_percent(self):
|
||||
for term_line in self:
|
||||
if term_line.value == 'percent' and (term_line.value_amount < 0.0 or term_line.value_amount > 100.0):
|
||||
raise ValidationError(_('Percentages on the Payment Terms lines must be between 0 and 100.'))
|
||||
if term_line.discount_percentage and (term_line.discount_percentage < 0.0 or term_line.discount_percentage > 100.0):
|
||||
raise ValidationError(_('Discount percentages on the Payment Terms lines must be between 0 and 100.'))
|
||||
|
||||
@api.constrains('discount_days')
|
||||
def _check_positive(self):
|
||||
for term_line in self:
|
||||
if term_line.discount_days < 0:
|
||||
raise ValidationError(_('The discount days of the Payment Terms lines must be positive.'))
|
||||
@api.depends('payment_id')
|
||||
def _compute_days(self):
|
||||
for line in self:
|
||||
#Line.payment_id.line_ids[-1] is the new line that has been just added when clicking "add a new line"
|
||||
if not line.nb_days and len(line.payment_id.line_ids) > 1:
|
||||
line.nb_days = line.payment_id.line_ids[-2].nb_days + 30
|
||||
else:
|
||||
line.nb_days = line.nb_days
|
||||
|
||||
@api.depends('payment_id')
|
||||
def _compute_value_amount(self):
|
||||
for line in self:
|
||||
if line.value == 'fixed':
|
||||
line.value_amount = 0
|
||||
else:
|
||||
amount = 0
|
||||
for i in line.payment_id.line_ids.filtered(lambda r: r.value == 'percent'):
|
||||
amount += i['value_amount']
|
||||
line.value_amount = 100 - amount
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue