trytond-sale_margin/sale.py

198 lines
7.4 KiB
Python

# This file is part sale_margin module for Tryton.
# The COPYRIGHT file at the top level of this repository contains
# the full copyright notices and license terms.
from decimal import Decimal
from math import fabs
from trytond.model import fields
from trytond.pyson import Eval
from trytond.pool import Pool, PoolMeta
from trytond.modules.product import price_digits
from trytond.modules.currency.fields import Monetary
__all__ = ['Sale', 'SaleLine']
_ZERO = Decimal(0)
class Sale(metaclass=PoolMeta):
__name__ = 'sale.sale'
margin = fields.Function(Monetary('Margin',
currency='currency', digits='currency',
help=('It gives profitability by calculating the difference '
'between the Unit Price and Cost Price.')),
'get_margin')
margin_cache = Monetary('Margin Cache',
currency='currency', digits='currency', readonly=True)
margin_percent = fields.Function(fields.Numeric('Margin (%)',
digits=(16, 4)), 'get_margin_percent')
margin_percent_cache = fields.Numeric('Margin (%) Cache',
digits=(16, 4), readonly=True)
@classmethod
def copy(cls, sales, default=None):
if default is None:
default = {}
default = default.copy()
default['margin_cache'] = None
default['margin_percent_cache'] = None
return super(Sale, cls).copy(sales, default=default)
def get_margin(self, name):
'''
Return the margin of each sales
'''
Currency = Pool().get('currency.currency')
if (self.state in self._states_cached
and self.margin_cache is not None):
return self.margin_cache
margin = sum((l.margin for l in self.lines if l.type == 'line'),
_ZERO)
return Currency.round(self.currency, margin)
def get_margin_percent(self, name):
Configuration = Pool().get('sale.configuration')
config = Configuration(1)
sale_margin_method = config.sale_margin_method or 'cost_price'
if (self.state in self._states_cached
and self.margin_percent_cache is not None):
return self.margin_percent_cache
price = sum(
Decimal(str(fabs(l.quantity))) * (
getattr(l, sale_margin_method) or _ZERO)
for l in self.lines if l.type == 'line')
if price:
return (self.margin / price).quantize(Decimal('0.0001'))
else:
return Decimal('1.0')
@classmethod
def store_cache(cls, sales):
super(Sale, cls).store_cache(sales)
for sale in sales:
cls.write([sale], {
'margin_cache': sale.margin,
'margin_percent_cache': sale.margin_percent,
})
class SaleLine(metaclass=PoolMeta):
__name__ = 'sale.line'
cost_price = fields.Numeric('Cost Price', digits=price_digits,
states={
'invisible': Eval('type') != 'line',
'readonly': ~Eval('sale_state').in_(['draft', 'quotation']),
},
depends=['type', 'sale_state'])
margin = fields.Function(Monetary('Margin',
currency='currency', digits='currency',
states={
'invisible': ~Eval('type').in_(['line', 'subtotal']),
'readonly': ~Eval('_parent_sale'),
},
depends=['type', 'amount']), 'on_change_with_margin')
margin_percent = fields.Function(fields.Numeric('Margin (%)',
digits=(16, 4), states={
'invisible': ~Eval('type').in_(['line', 'subtotal']),
}, depends=['type']), 'on_change_with_margin_percent')
@staticmethod
def default_cost_price():
return _ZERO
@fields.depends('product', 'currency', 'sale')
def on_change_product(self):
Currency = Pool().get('currency.currency')
super(SaleLine, self).on_change_product()
if self.product:
if self.currency and self.sale:
cost_price = Currency.compute(
self.currency, self.product.cost_price,
self.sale.currency, round=False)
else:
cost_price = self.product.cost_price
self.cost_price = cost_price
@fields.depends('type', 'quantity', 'cost_price', '_parent_sale.currency',
'_parent_sale.lines', methods=['on_change_with_amount'])
def on_change_with_margin(self, name=None):
'''
Return the margin of each sale lines
'''
Currency = Pool().get('currency.currency')
cost = _ZERO
if self.sale and self.sale.currency:
currency = self.sale.currency
if self.type == 'line':
if self.quantity and self.cost_price:
cost = Decimal(str(self.quantity)) * (self.cost_price)
self.amount = self.on_change_with_amount()
return Currency.round(currency, self.amount - cost)
elif self.type == 'subtotal':
for line2 in self.sale.lines:
if self == line2:
return cost
if line2.type == 'line':
cost_price = line2.cost_price or _ZERO
cost2 = Decimal(str(line2.quantity)) * cost_price
cost += Currency.round(currency, line2.amount - cost2)
elif line2.type == 'subtotal':
cost = _ZERO
return cost
@fields.depends('type', 'quantity', 'cost_price', 'unit_price',
'_parent_sale.currency', '_parent_sale.lines',
methods=['on_change_with_margin'])
def on_change_with_margin_percent(self, name=None):
Configuration = Pool().get('sale.configuration')
config = Configuration(1)
sale_margin_method = config.sale_margin_method or 'cost_price'
if self.type not in ('line', 'subtotal'):
return
self.margin = self.on_change_with_margin()
if not self.margin:
return
price = _ZERO
if self.type == 'line':
if not self.quantity:
return
if sale_margin_method == 'cost_price' and not self.cost_price:
return Decimal('1.0')
if sale_margin_method == 'unit_price' and not self.unit_price:
return Decimal('1.0')
if sale_margin_method == 'unit_price':
price = self.get_margin_unit_price()
else:
price = self.get_margin_cost_price()
return (self.margin / price).quantize(Decimal('0.0001'))
else:
for line2 in self.sale.lines:
if self == line2:
if not price:
return Decimal('1.0')
else:
return (
self.margin / price).quantize(Decimal('0.0001'))
if line2.type == 'line':
if sale_margin_method == 'unit_price':
price += line2.get_margin_unit_price()
else:
price += line2.get_margin_cost_price()
elif line2.type == 'subtotal':
price = _ZERO
return price
def get_margin_cost_price(self):
return Decimal(str(fabs(self.quantity))) * (self.cost_price or
_ZERO)
def get_margin_unit_price(self):
return Decimal(str(fabs(self.quantity))) * (self.unit_price or
_ZERO)