trytond-sale_commission/sale.py

275 lines
9.6 KiB
Python

# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
from decimal import Decimal
from itertools import groupby
from trytond.model import ModelView, ModelSQL, fields
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Bool, Eval
__all__ = ['Configuration', 'CommissionTypeCategory', 'CommissionType',
'CommissionTypeParty', 'CommissionTypeLine', 'Party', 'Sale', 'Invoice',
'InvoiceLine']
__metaclass__ = PoolMeta
class Configuration:
__name__ = 'sale.configuration'
commission_product = fields.Many2One('product.product',
'Commission Product',
domain=[
('type', '=', 'service'),
],
help='Product used to generate in invoices for sale commisions.')
class CommissionTypeCategory(ModelSQL, ModelView):
'Commission Category'
__name__ = 'commission.type.category'
name = fields.Char('Name', required=True, translate=True)
class CommissionTypeParty(ModelSQL):
'CommissionType - Party'
__name__ = 'commission.type-party.party'
party = fields.Many2One('commission.type', 'CommissionType',
ondelete='CASCADE', required=True, select=True)
commission_type = fields.Many2One('party.party', 'PartyParty',
ondelete='CASCADE', required=True, select=True)
class CommissionType(ModelSQL, ModelView):
'Commission Type'
__name__ = 'commission.type'
name = fields.Char('Name', translate=True, required=True)
lines = fields.One2Many('commission.type.line', 'commission_type',
'Commission Lines', required=True)
parties = fields.Many2Many('commission.type-party.party',
'party', 'commission_type', 'Parties',
domain=[
('is_middleman', '=', True),
])
class CommissionTypeLine(ModelSQL, ModelView):
'CommissionType'
__name__ = 'commission.type.line'
commission_type = fields.Many2One('commission.type', 'Commission Type',
required=True, select=True)
category = fields.Many2One('commission.type.category', 'Category',
required=True, select=True)
percent = fields.Numeric('Percent', digits=(16, 4), required=True)
def get_rec_name(self, name):
return self.commission_type.rec_name
@classmethod
def __setup__(cls):
super(CommissionTypeLine, cls).__setup__()
cls._error_messages.update({
'no_product_config': ('There is no product defined for '
'commissions. Please define on in sale configuration.'),
'missing_expense_account': ('Product "%s" used to generate '
'commissions misses an expense account.'),
'invoice_description': ('Commission "%(commission)s" for '
'payment of invoice %(invoice)s with date %(date)s.'),
})
def is_applicable_on_sale(self, sale):
'Returns if the commission line is applicable for sale sale'
return True
def get_invoice_line(self, key, commissions):
'''
Returns the invoice line for the current commission
Key is a dict with the key used to group commissions and
commissions is a list of the commissions to group
'''
pool = Pool()
InvoiceLine = pool.get('account.invoice.line')
Config = pool.get('sale.configuration')
amount = sum(c['amount'] for c in commissions)
if not amount:
return
config = Config(1)
if not config.commission_product:
self.raise_user_error('no_product_config')
product = config.commission_product
if not product.account_expense_used:
self.raise_user_error('missing_expense_account', product.rec_name)
line = InvoiceLine()
line.party = key['middleman']
line.type = 'line'
line.invoice_type = 'in_invoice'
line.description = self.get_invoice_line_description(key, commissions)
line.product = product
line.account = product.account_expense_used
line.currency = InvoiceLine.default_currency()
for k, v in line.on_change_product().iteritems():
setattr(line, k, v)
line.quantity = 1.0
line.unit_price = Decimal(str(self.percent)) * amount
line.origin = key['origin']
return line
def get_invoice_line_description(self, key, commissions):
params = {
'commission': self.rec_name,
'invoice': '-'.join(c['invoice'] for c in commissions),
'date': key['date'],
}
return self.raise_user_error('invoice_description', params,
raise_exception=False)
class Party:
__name__ = 'party.party'
is_middleman = fields.Boolean('Middleman', help='Indicates if the party'
' is a middleman and gets commissions.')
middleman = fields.Many2One('party.party', 'Middleman',
domain=[
('is_middleman', '=', True),
],
states={
'invisible': Bool(Eval('is_middleman')),
},
depends=['middleman'])
commissions = fields.Many2Many('commission.type-party.party',
'commission_type', 'party', 'Commissions',
states={
'invisible': ~Bool(Eval('is_middleman')),
},
depends=['middleman'])
class Sale:
__name__ = 'sale.sale'
middleman = fields.Many2One('party.party', 'Middleman',
domain=[
('is_middleman', '=', True),
],
states={
'readonly': Eval('state') != 'draft',
},
depends=['state'])
commission_type = fields.Many2One('commission.type', 'Commission Type',
states={
'invisible': ~Bool(Eval('middleman')),
'required': Bool(Eval('middleman')),
'readonly': Eval('state') != 'draft',
},
depends=['middleman', 'state'])
@fields.depends('middleman')
def on_change_party(self):
changes = super(Sale, self).on_change_party()
changes['middleman'] = None
self.middleman = None
if self.party and self.party.middleman:
changes['middleman'] = self.party.middleman.id
changes['middleman.rec_name'] = self.party.middleman.rec_name
self.middleman = self.party.middleman
changes.update(self.on_change_middleman())
return changes
@fields.depends('middleman')
def on_change_middleman(self):
changes = {
'commission_type': None,
}
if self.middleman and self.middleman.commissions:
changes['commission_type'] = self.middleman.commissions[0].id
return changes
class Invoice:
__name__ = 'account.invoice'
@classmethod
def _group_commission_key(cls, commission):
'''
The key to group commissions
commission is a dict with the values to create the commission
'''
return (
('middleman', commission['middleman']),
('date', commission['date']),
('origin', commission['origin']),
('commission', commission['commission']),
)
@classmethod
def process(cls, invoices):
pool = Pool()
InvoiceLine = pool.get('account.invoice.line')
Currency = pool.get('currency.currency')
super(Invoice, cls).process(invoices)
commissions = []
for invoice in invoices:
if not invoice.move:
continue
middlemans_commissions = set()
for sale in invoice.sales:
if not sale.middleman or not sale.commission_type:
continue
for line in sale.commission_type.lines:
if not line.is_applicable_on_sale(sale):
continue
middlemans_commissions.add((sale.middleman, line))
if not middlemans_commissions:
continue
total = Currency.compute(invoice.currency, invoice.untaxed_amount,
invoice.company.currency)
term_lines = invoice.payment_term.compute(total,
invoice.company.currency, invoice.invoice_date)
if not term_lines:
term_lines = [(invoice.invoice_date, total)]
for (date, amount), line in zip(term_lines, invoice.lines_to_pay):
if not line.reconciliation:
continue
existing_lines = InvoiceLine.search([
('origin', '=', str(line)),
])
if existing_lines:
continue
for middleman, commission in middlemans_commissions:
commissions.append({
'amount': amount,
'date': date,
'middleman': middleman.id,
'commission': commission,
'invoice': invoice.rec_name,
'origin': str(line),
})
keyfunc = cls._group_commission_key
commissions = sorted(commissions, key=keyfunc)
to_create = []
for key, grouped_commissions in groupby(commissions, key=keyfunc):
key = dict(key)
commission = key['commission']
grouped_commissions = list(grouped_commissions)
line = commission.get_invoice_line(key, grouped_commissions)
to_create.append(line._save_values)
if to_create:
InvoiceLine.create(to_create)
class InvoiceLine:
__name__ = 'account.invoice.line'
@classmethod
def _get_origin(cls):
origins = super(InvoiceLine, cls)._get_origin()
origins.append('account.move.line')
return origins