trytond-carrier_formula/carrier.py

226 lines
8.1 KiB
Python

# This file is part carrier_formula 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 simpleeval import simple_eval
from trytond import backend
from trytond.model import ModelSQL, ModelView, MatchMixin, sequence_ordered, fields
from trytond.pyson import Eval, Bool
from trytond.pool import Pool, PoolMeta
from trytond.transaction import Transaction
from trytond.tools import decistmt
from trytond.modules.product import price_digits
__all__ = ['Carrier', 'FormulaPriceList']
class Carrier(metaclass=PoolMeta):
__name__ = 'carrier'
formula_currency = fields.Many2One('currency.currency', 'Currency',
states={
'invisible': Eval('carrier_cost_method') != 'formula',
'required': Eval('carrier_cost_method') == 'formula',
'readonly': Bool(Eval('formula_price_list', [])),
},
depends=['carrier_cost_method', 'formula_price_list'])
formula_price_list = fields.One2Many(
'carrier.formula_price_list', 'carrier', 'Price List',
states={
'invisible': Eval('carrier_cost_method') != 'formula',
},
depends=['carrier_cost_method', 'formula_currency'])
@classmethod
def __setup__(cls):
super(Carrier, cls).__setup__()
selection = ('formula', 'Formula')
if selection not in cls.carrier_cost_method.selection:
cls.carrier_cost_method.selection.append(selection)
@staticmethod
def default_formula_currency():
Company = Pool().get('company.company')
company = Transaction().context.get('company')
if company:
return Company(company).currency.id
@staticmethod
def round_price_formula(number, digits):
quantize = Decimal(10) ** -Decimal(digits)
return Decimal(number).quantize(quantize)
def get_context_formula(self, record):
return {
'names': {
'record': record,
},
'functions': {
'Decimal': Decimal,
'round': round,
},
}
def get_formula_pattern(self, record):
return {}
def compute_formula_price(self, record):
"Compute price based on formula"
context = self.get_context_formula(record)
pattern = self.get_formula_pattern(record)
for line in self.formula_price_list:
if line.match(pattern):
if simple_eval(decistmt(line.formula), **context):
return line.get_unit_price()
return Decimal(0)
def get_sale_price(self):
# Designed to get shipment price with a current sale (default)
# or calculate prices for a hipotetic order (sale cart).
# Second case, we add in context a carrier and simulate a sale in
# record to evaluate (safe eval) in carrier formula.
# Example:
# sale = Sale()
# sale.untaxed_amount = untaxed_amount
# sale.tax_amount = tax_amount
# sale.total_amount = total_amount
# context = {}
# context['record'] = record._save_values
# context['record_model'] = 'sale.sale'
# context['carrier'] = carrier
price, currency_id = super(Carrier, self).get_sale_price()
if self.carrier_cost_method == 'formula':
price = Decimal(0)
currency_id = self.formula_currency.id
carrier = Transaction().context.get('carrier', None)
record = Transaction().context.get('record', None)
model = Transaction().context.get('record_model', None)
if not record:
return price, currency_id
# is an object that not saved (has not id)
if isinstance(record, dict):
if not model:
return price, currency_id
record = Pool().get(model)(**record)
elif isinstance(record, str):
model, id = record.split(',')
record = Pool().get(model)(id)
if carrier:
price = self.compute_formula_price(record)
else:
if model == 'sale.sale':
if record.carrier:
record.untaxed_amount = Decimal(0)
for line in record.lines:
if (hasattr(line, 'shipment_cost')
and line.shipment_cost):
continue
if line.amount and line.type == 'line':
record.untaxed_amount += line.amount
record.tax_amount = record.get_tax_amount()
record.total_amount = (
record.untaxed_amount + record.tax_amount)
price = self.compute_formula_price(record)
else:
price = self.carrier_product.list_price
price = self.round_price_formula(price, self.formula_currency.digits)
return price, currency_id
def get_purchase_price(self):
price, currency_id = super(Carrier, self).get_purchase_price()
if self.carrier_cost_method == 'formula':
price = Decimal(0)
currency_id = self.formula_currency.id
record = Transaction().context.get('record', None)
model = Transaction().context.get('record_model', None)
if not record:
return price, currency_id
# is an object that not saved (has not id)
if isinstance(record, dict):
if not model:
return price, currency_id
record = Pool().get(model)(**record)
else:
model, id = record.split(',')
record = Pool().get(model)(id)
if model == 'purchase.purchase':
if record.carrier:
record.untaxed_amount = Decimal(0)
for line in record['lines']:
if (not line.shipment_cost
and line.amount
and line.type == 'line'):
record.untaxed_amount += line.amount
record.tax_amount = record.get_tax_amount()
record.total_amount = (
record.untaxed_amount + record.tax_amount)
price = self.compute_formula_price(record)
else:
price = self.carrier_product.list_price
price = self.round_price_formula(price, self.formula_currency.digits)
return price, currency_id
class FormulaPriceList(sequence_ordered(), ModelSQL, ModelView, MatchMixin):
'Carrier Formula Price List'
__name__ = 'carrier.formula_price_list'
carrier = fields.Many2One('carrier', 'Carrier', required=True)
sequence = fields.Integer('Sequence', required=True)
formula = fields.Char('Formula', required=True,
help=('Python expression that will be evaluated. Eg:\n'
'getattr(record, "total_amount") > 0'))
price = fields.Numeric('Price', required=True, digits=price_digits)
@classmethod
def __setup__(cls):
super(FormulaPriceList, cls).__setup__()
cls._order.insert(0, ('sequence', 'ASC'))
@classmethod
def __register__(cls, module_name):
super(FormulaPriceList, cls).__register__(module_name)
table_h = backend.TableHandler(cls, module_name)
# Migration from 4.1
table_h.not_null_action('sequence', 'remove')
@staticmethod
def default_formula():
return 'getattr(record, "total_amount") > 0'
@staticmethod
def default_price():
return Decimal(0)
@classmethod
def validate(cls, lines):
super(FormulaPriceList, cls).validate(lines)
for line in lines:
line.check_formula()
def check_formula(self):
'''
Check formula
'''
return True
def match(self, pattern):
return super(FormulaPriceList, self).match(pattern)
def get_unit_price(self):
return self.price