2014-04-10 17:19:39 +02:00
|
|
|
# The COPYRIGHT file at the top level of this repository contains the full
|
|
|
|
# copyright notices and license terms.
|
|
|
|
from decimal import Decimal
|
|
|
|
|
2013-09-18 10:09:46 +02:00
|
|
|
from trytond.model import ModelSQL, ModelView, fields
|
|
|
|
from trytond.pool import Pool, PoolMeta
|
|
|
|
from trytond.pyson import Eval
|
2014-04-10 17:19:39 +02:00
|
|
|
from trytond.transaction import Transaction
|
2016-01-12 11:00:41 +01:00
|
|
|
from trytond.modules.product import price_digits
|
2013-09-18 10:09:46 +02:00
|
|
|
|
2014-04-10 17:19:39 +02:00
|
|
|
__all__ = ['LotCostCategory', 'LotCostLine', 'Lot', 'Move', 'Product',
|
|
|
|
'Location']
|
2013-09-18 10:09:46 +02:00
|
|
|
__metaclass__ = PoolMeta
|
|
|
|
|
|
|
|
|
|
|
|
class LotCostCategory(ModelSQL, ModelView):
|
|
|
|
'''Stock Lot Cost Category'''
|
|
|
|
__name__ = 'stock.lot.cost_category'
|
|
|
|
|
|
|
|
name = fields.Char('Name', translate=True, required=True)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def __setup__(cls):
|
|
|
|
super(LotCostCategory, cls).__setup__()
|
|
|
|
cls._sql_constraints += [
|
|
|
|
('name_uniq', 'UNIQUE (name)',
|
|
|
|
'The Name of the Lot Cost Category must be unique.'),
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
class LotCostLine(ModelSQL, ModelView):
|
|
|
|
'''Stock Lot Cost Line'''
|
|
|
|
__name__ = 'stock.lot.cost_line'
|
|
|
|
|
|
|
|
lot = fields.Many2One('stock.lot', 'Lot', required=True, select=True,
|
|
|
|
ondelete='CASCADE')
|
|
|
|
category = fields.Many2One('stock.lot.cost_category', 'Category',
|
|
|
|
required=True)
|
2016-01-12 11:00:41 +01:00
|
|
|
unit_price = fields.Numeric('Unit Price', digits=price_digits, required=True)
|
2013-10-10 12:35:20 +02:00
|
|
|
origin = fields.Reference('Origin', selection='get_origin', readonly=True,
|
2013-09-18 10:09:46 +02:00
|
|
|
select=True)
|
|
|
|
|
2013-10-10 12:35:20 +02:00
|
|
|
@classmethod
|
|
|
|
def _get_origin(cls):
|
|
|
|
'Return list of Model names for origin Reference'
|
|
|
|
return [
|
|
|
|
'stock.move',
|
|
|
|
]
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_origin(cls):
|
|
|
|
pool = Pool()
|
|
|
|
Model = pool.get('ir.model')
|
|
|
|
models = cls._get_origin()
|
|
|
|
models = Model.search([
|
|
|
|
('model', 'in', models),
|
|
|
|
])
|
|
|
|
return [('', '')] + [(m.model, m.name) for m in models]
|
|
|
|
|
2013-09-18 10:09:46 +02:00
|
|
|
|
|
|
|
class Lot:
|
|
|
|
__name__ = 'stock.lot'
|
|
|
|
|
|
|
|
cost_lines = fields.One2Many('stock.lot.cost_line', 'lot', 'Cost Lines')
|
2016-01-12 11:00:41 +01:00
|
|
|
cost_price = fields.Function(fields.Numeric('Cost Price',
|
|
|
|
digits=price_digits),
|
2013-09-18 10:09:46 +02:00
|
|
|
'get_cost_price')
|
|
|
|
|
|
|
|
def get_cost_price(self, name):
|
2014-04-10 17:19:39 +02:00
|
|
|
return (sum(l.unit_price for l in self.cost_lines)
|
|
|
|
if self.cost_lines else None)
|
2013-09-18 10:09:46 +02:00
|
|
|
|
2014-06-03 13:00:44 +02:00
|
|
|
@fields.depends('product', 'cost_lines')
|
2013-09-18 10:09:46 +02:00
|
|
|
def on_change_product(self):
|
|
|
|
try:
|
|
|
|
result = super(Lot, self).on_change_product()
|
|
|
|
except AttributeError:
|
|
|
|
result = {}
|
|
|
|
|
|
|
|
cost_lines = self._on_change_product_cost_lines()
|
|
|
|
if cost_lines:
|
|
|
|
result['cost_lines'] = cost_lines
|
|
|
|
return result
|
|
|
|
|
|
|
|
def _on_change_product_cost_lines(self):
|
|
|
|
pool = Pool()
|
|
|
|
ModelData = pool.get('ir.model.data')
|
|
|
|
|
|
|
|
if not self.product:
|
|
|
|
return None
|
|
|
|
|
|
|
|
category_id = ModelData.get_id('stock_lot_cost',
|
|
|
|
'cost_category_standard_price')
|
|
|
|
return {
|
2014-06-03 13:00:44 +02:00
|
|
|
'add': [(0, {
|
2014-04-10 17:19:39 +02:00
|
|
|
'category': category_id,
|
|
|
|
'unit_price': self.product.cost_price,
|
2014-06-03 13:00:44 +02:00
|
|
|
})],
|
2013-09-18 10:09:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class Move:
|
|
|
|
__name__ = 'stock.move'
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def __setup__(cls):
|
|
|
|
super(Move, cls).__setup__()
|
|
|
|
cls.lot.context['from_move'] = Eval('id')
|
2014-04-10 17:19:39 +02:00
|
|
|
|
|
|
|
|
|
|
|
class Product:
|
|
|
|
__name__ = 'product.product'
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_cost_value(cls, products, name):
|
|
|
|
pool = Pool()
|
|
|
|
Lot = pool.get('stock.lot')
|
|
|
|
|
|
|
|
product_by_id = dict((p.id, p) for p in products)
|
|
|
|
cost_values = {}.fromkeys(product_by_id.keys(), None)
|
|
|
|
|
|
|
|
context = {}
|
|
|
|
trans_context = Transaction().context
|
|
|
|
if 'stock_date_end' in context:
|
|
|
|
context['_datetime'] = trans_context['stock_date_end']
|
|
|
|
location_ids = trans_context.get('locations')
|
|
|
|
with Transaction().set_context(context):
|
|
|
|
pbl = cls.products_by_location(location_ids=location_ids,
|
|
|
|
product_ids=product_by_id.keys(), with_childs=True,
|
|
|
|
grouping=('product', 'lot'))
|
|
|
|
|
|
|
|
for (location_id, product_id, lot_id), qty in pbl.iteritems():
|
|
|
|
cost_value = None
|
|
|
|
if lot_id:
|
|
|
|
lot = Lot(lot_id)
|
|
|
|
if isinstance(lot.cost_price, Decimal):
|
|
|
|
cost_value = (Decimal(str(qty)) * lot.cost_price)
|
|
|
|
else:
|
|
|
|
product = product_by_id[product_id]
|
|
|
|
if isinstance(product.cost_price, Decimal):
|
|
|
|
cost_value = (Decimal(str(qty)) * product.cost_price)
|
|
|
|
|
|
|
|
if cost_value is not None:
|
|
|
|
if cost_values[product_id] is not None:
|
|
|
|
cost_value += cost_values[product_id]
|
|
|
|
cost_values[product_id] = cost_value
|
|
|
|
return cost_values
|
|
|
|
|
|
|
|
|
|
|
|
class Location:
|
|
|
|
__name__ = 'stock.location'
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_cost_value(cls, locations, name):
|
|
|
|
pool = Pool()
|
|
|
|
Lot = pool.get('stock.lot')
|
|
|
|
Product = pool.get('product.product')
|
|
|
|
|
|
|
|
trans_context = Transaction().context
|
|
|
|
product_id = trans_context.get('product')
|
|
|
|
lot_id = trans_context.get('lot')
|
|
|
|
if not product_id and not lot_id:
|
|
|
|
return dict((l.id, None) for l in locations)
|
|
|
|
|
|
|
|
cost_values, context = {}, {}
|
|
|
|
if 'stock_date_end' in context:
|
|
|
|
context['_datetime'] = trans_context['stock_date_end']
|
|
|
|
with Transaction().set_context(context):
|
|
|
|
if lot_id:
|
|
|
|
lot = Lot(lot_id)
|
|
|
|
for location in locations:
|
|
|
|
# The date could be before the product creation
|
|
|
|
if not isinstance(lot.cost_price, Decimal):
|
|
|
|
cost_values[location.id] = None
|
|
|
|
else:
|
|
|
|
cost_values[location.id] = (
|
|
|
|
Decimal(str(location.quantity)) * lot.cost_price)
|
|
|
|
else:
|
|
|
|
product = Product(product_id)
|
2014-06-20 01:15:09 +02:00
|
|
|
pbl = Product.products_by_location(
|
2014-04-10 17:19:39 +02:00
|
|
|
location_ids=[l.id for l in locations],
|
|
|
|
product_ids=[product_id], with_childs=True,
|
|
|
|
grouping=('product', 'lot'))
|
|
|
|
|
|
|
|
cost_values = dict((l.id, None) for l in locations)
|
2015-05-19 19:32:39 +02:00
|
|
|
for key, qty in pbl.iteritems():
|
|
|
|
if len(key) != 3:
|
|
|
|
continue
|
|
|
|
location_id, product_id, lot_id = key
|
2014-04-10 17:19:39 +02:00
|
|
|
cost_value = None
|
|
|
|
if lot_id:
|
|
|
|
lot = Lot(lot_id)
|
|
|
|
if isinstance(lot.cost_price, Decimal):
|
|
|
|
cost_value = (Decimal(str(qty)) * lot.cost_price)
|
|
|
|
else:
|
|
|
|
if isinstance(product.cost_price, Decimal):
|
|
|
|
cost_value = (Decimal(str(qty))
|
|
|
|
* product.cost_price)
|
|
|
|
|
|
|
|
if cost_value is not None:
|
|
|
|
if cost_values[location_id] is not None:
|
|
|
|
cost_value += cost_values[location_id]
|
|
|
|
cost_values[location_id] = cost_value
|
|
|
|
return cost_values
|