Files
tradon/modules/account_dunning_fee/dunning.py
2026-03-14 09:42:12 +00:00

194 lines
6.6 KiB
Python

# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from collections import defaultdict
from trytond.model import DeactivableMixin, ModelSQL, ModelView, Unique, fields
from trytond.modules.currency.fields import Monetary
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
from trytond.transaction import Transaction
class Fee(DeactivableMixin, ModelSQL, ModelView):
__name__ = 'account.dunning.fee'
name = fields.Char('Name', required=True, translate=True)
product = fields.Many2One('product.product', 'Product', required=True,
domain=[
('type', '=', 'service'),
('template.type', '=', 'service'),
])
journal = fields.Many2One('account.journal', 'Journal', required=True)
compute_method = fields.Selection([
('list_price', 'List Price'),
('percentage', 'Percentage'),
], 'Compute Method', required=True,
help='Method to compute the fee amount')
percentage = fields.Numeric(
"Percentage", digits=(None, 8),
states={
'invisible': Eval('compute_method') != 'percentage',
'required': Eval('compute_method') == 'percentage',
})
def get_list_price(self, dunning):
pool = Pool()
Product = pool.get('product.product')
with Transaction().set_context(company=dunning.company.id):
product = Product(self.product)
return product.list_price_used
def get_amount(self, dunning):
'Return fee amount and currency'
amount, currency = None, None
if self.compute_method == 'list_price':
currency = dunning.company.currency
amount = currency.round(self.get_list_price(dunning))
elif self.compute_method == 'percentage':
if dunning.second_currency:
amount = dunning.amount_second_currency
currency = dunning.second_currency
else:
amount = dunning.amount
currency = dunning.company.currency
amount = currency.round(amount * self.percentage)
return amount, currency
class Level(metaclass=PoolMeta):
__name__ = 'account.dunning.level'
fee = fields.Many2One('account.dunning.fee', 'Fee')
class Dunning(metaclass=PoolMeta):
__name__ = 'account.dunning'
fees = fields.One2Many(
'account.dunning.fee.dunning_level', 'dunning', 'Fees', readonly=True)
@classmethod
def process(cls, dunnings):
pool = Pool()
FeeDunningLevel = pool.get('account.dunning.fee.dunning_level')
fees = []
for dunning in dunnings:
if dunning.blocked or not dunning.level.fee:
continue
if dunning.level in {f.level for f in dunning.fees}:
continue
fee = FeeDunningLevel(dunning=dunning, level=dunning.level)
fee.amount, fee.currency = dunning.level.fee.get_amount(dunning)
fees.append(fee)
FeeDunningLevel.save(fees)
FeeDunningLevel.process(fees)
super().process(dunnings)
class FeeDunningLevel(ModelSQL, ModelView):
__name__ = 'account.dunning.fee.dunning_level'
dunning = fields.Many2One(
'account.dunning', "Dunning", required=True)
level = fields.Many2One('account.dunning.level', 'Level', required=True)
amount = Monetary(
"Amount", currency='currency', digits='currency')
currency = fields.Many2One('currency.currency', 'Currency')
moves = fields.One2Many('account.move', 'origin', 'Moves', readonly=True)
@classmethod
def __setup__(cls):
super().__setup__()
t = cls.__table__()
cls._sql_constraints = [
('dunning_level_unique', Unique(t, t.dunning, t.level),
'account_dunning_fee.msg_fee_dunning_level_unique'),
]
def get_rec_name(self, name):
return '%s @ %s' % (self.dunning.rec_name, self.level.rec_name)
@classmethod
def search_rec_name(cls, name, clause):
_, operator, value = clause
if operator.startswith('!') or operator.startswith('not '):
bool_op = 'AND'
else:
bool_op = 'OR'
return [bool_op,
('dunning.rec_name', *clause[1:]),
('level.rec_name', *clause[1:]),
]
@classmethod
def process(cls, fees):
pool = Pool()
Move = pool.get('account.move')
moves = []
for fee in fees:
move = fee.get_move_process()
moves.append(move)
Move.save(moves)
Move.post(moves)
def get_move_process(self):
pool = Pool()
Move = pool.get('account.move')
Line = pool.get('account.move.line')
Date = pool.get('ir.date')
Period = pool.get('account.period')
Currency = pool.get('currency.currency')
with Transaction().set_context(company=self.dunning.company.id):
today = Date.today()
move = Move()
move.company = self.dunning.company
move.journal = self.level.fee.journal
move.date = today
move.period = Period.find(move.company, date=today)
move.origin = self
move.description = self.level.fee.name
line = Line()
if self.currency == move.company.currency:
line.debit = self.amount
else:
line.second_currency = self.currency
line.amount_second_currency = self.amount
line.debit = Currency.compute(
self.currency, self.amount, move.company.currency)
line.account = self.dunning.line.account
line.party = self.dunning.line.party
counterpart = Line()
counterpart.credit = line.debit
counterpart.account = self.level.fee.product.account_revenue_used
if counterpart.account and counterpart.account.party_required:
counterpart.party = self.dunning.party
move.lines = [line, counterpart]
return move
# TODO create move with taxes on reconcile of process move line
class Letter(metaclass=PoolMeta):
__name__ = 'account.dunning.letter'
@classmethod
def get_party_letter(cls):
PartyLetter = super().get_party_letter()
class PartyLetterFee(PartyLetter):
@property
def fees(self):
fees = defaultdict(int)
fees.update(super().fees)
for dunning in self.dunnings:
for fee in dunning.fees:
fees[fee.currency] += fee.amount
return fees
return PartyLetterFee