first commit
This commit is contained in:
193
modules/account_dunning_fee/dunning.py
Normal file
193
modules/account_dunning_fee/dunning.py
Normal file
@@ -0,0 +1,193 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user