239 lines
9.1 KiB
Python
239 lines
9.1 KiB
Python
# 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 decimal import Decimal
|
|
|
|
from trytond.i18n import gettext
|
|
from trytond.model import fields
|
|
from trytond.modules.product import round_price
|
|
from trytond.modules.stock.exceptions import MoveValidationError
|
|
from trytond.pool import Pool, PoolMeta
|
|
from trytond.pyson import Bool, Eval
|
|
from trytond.transaction import Transaction
|
|
|
|
|
|
class Configuration(metaclass=PoolMeta):
|
|
__name__ = 'stock.configuration'
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls.shipment_internal_transit.domain = [
|
|
cls.shipment_internal_transit.domain,
|
|
('cost_warehouse', '=', None),
|
|
]
|
|
|
|
|
|
class ConfigurationLocation(metaclass=PoolMeta):
|
|
__name__ = 'stock.configuration.location'
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super().__setup__()
|
|
cls.shipment_internal_transit.domain = [
|
|
cls.shipment_internal_transit.domain,
|
|
('cost_warehouse', '=', None),
|
|
]
|
|
|
|
|
|
class Location(metaclass=PoolMeta):
|
|
__name__ = 'stock.location'
|
|
|
|
cost_warehouse = fields.Many2One(
|
|
'stock.location', "Cost Warehouse",
|
|
domain=[
|
|
('type', '=', 'warehouse'),
|
|
],
|
|
states={
|
|
'invisible': (
|
|
(Eval('type') != 'storage')
|
|
| Bool(Eval('warehouse'))
|
|
| (Eval('id', -1) < 0)),
|
|
})
|
|
|
|
|
|
class Move(metaclass=PoolMeta):
|
|
__name__ = 'stock.move'
|
|
|
|
@property
|
|
@fields.depends('from_location')
|
|
def from_cost_warehouse(self):
|
|
if self.from_location:
|
|
return (
|
|
self.from_location.warehouse
|
|
or self.from_location.cost_warehouse)
|
|
|
|
@property
|
|
@fields.depends('to_location')
|
|
def to_cost_warehouse(self):
|
|
if self.to_location:
|
|
return (
|
|
self.to_location.warehouse
|
|
or self.to_location.cost_warehouse)
|
|
|
|
@property
|
|
def cost_warehouse(self):
|
|
return self.from_cost_warehouse or self.to_cost_warehouse
|
|
|
|
@fields.depends(
|
|
'company', methods=['from_cost_warehouse', 'to_cost_warehouse'])
|
|
def on_change_with_unit_price_required(self, name=None):
|
|
required = super().on_change_with_unit_price_required(name=name)
|
|
if (self.company and self.company.cost_price_warehouse
|
|
and self.from_cost_warehouse != self.to_cost_warehouse):
|
|
required = True
|
|
return required
|
|
|
|
@fields.depends(
|
|
'company', methods=['from_cost_warehouse', 'to_cost_warehouse'])
|
|
def on_change_with_cost_price_required(self, name=None):
|
|
required = super().on_change_with_cost_price_required(name=name)
|
|
if (self.company and self.company.cost_price_warehouse
|
|
and self.from_cost_warehouse != self.to_cost_warehouse):
|
|
required = True
|
|
return required
|
|
|
|
@classmethod
|
|
def get_unit_price_company(cls, moves, name):
|
|
pool = Pool()
|
|
ShipmentInternal = pool.get('stock.shipment.internal')
|
|
Uom = pool.get('product.uom')
|
|
prices = super().get_unit_price_company(moves, name)
|
|
for move in moves:
|
|
if (move.company.cost_price_warehouse
|
|
and move.from_cost_warehouse != move.to_cost_warehouse
|
|
and move.to_cost_warehouse
|
|
and isinstance(move.shipment, ShipmentInternal)):
|
|
cost = total_qty = 0
|
|
for outgoing_move in move.shipment.outgoing_moves:
|
|
if outgoing_move.product == move.product:
|
|
qty = Uom.compute_qty(
|
|
outgoing_move.unit, outgoing_move.quantity,
|
|
move.product.default_uom)
|
|
qty = Decimal(str(qty))
|
|
cost += qty * outgoing_move.cost_price
|
|
total_qty += qty
|
|
if cost and total_qty:
|
|
cost_price = round_price(cost / total_qty)
|
|
prices[move.id] = cost_price
|
|
return prices
|
|
|
|
def get_cost_price(self, product_cost_price=None):
|
|
pool = Pool()
|
|
ShipmentInternal = pool.get('stock.shipment.internal')
|
|
cost_price = super().get_cost_price(
|
|
product_cost_price=product_cost_price)
|
|
if (self.company.cost_price_warehouse
|
|
and self.from_cost_warehouse != self.to_cost_warehouse
|
|
and self.to_cost_warehouse
|
|
and isinstance(self.shipment, ShipmentInternal)):
|
|
cost_price = self.unit_price_company
|
|
return cost_price
|
|
|
|
@classmethod
|
|
def validate(cls, moves):
|
|
pool = Pool()
|
|
Configuration = pool.get('stock.configuration')
|
|
super().validate(moves)
|
|
config = Configuration(1)
|
|
transit_locations = {}
|
|
for move in moves:
|
|
if move.state in {'staging', 'draft'}:
|
|
continue
|
|
company = move.company
|
|
if company not in transit_locations:
|
|
transit_location = config.get_multivalue(
|
|
'shipment_internal_transit', company=company)
|
|
transit_locations[company] = transit_location
|
|
else:
|
|
transit_location = transit_locations[company]
|
|
if (company.cost_price_warehouse
|
|
and move.from_location.type == 'storage'
|
|
and move.from_location != transit_location
|
|
and move.to_location.type == 'storage'
|
|
and move.to_location != transit_location):
|
|
if move.from_cost_warehouse != move.to_cost_warehouse:
|
|
raise MoveValidationError(gettext(
|
|
'product_cost_warehouse'
|
|
'.msg_move_storage_location_same_warehouse',
|
|
from_=move.from_location.rec_name,
|
|
to=move.to_location.rec_name))
|
|
|
|
def _do(self):
|
|
cost_price, to_save = super()._do()
|
|
if (self.company.cost_price_warehouse
|
|
and self.from_location.type == 'storage'
|
|
and self.to_location.type == 'storage'
|
|
and self.from_cost_warehouse != self.to_cost_warehouse):
|
|
if self.from_cost_warehouse:
|
|
cost_price = self._compute_product_cost_price('out')
|
|
elif self.to_cost_warehouse:
|
|
cost_price = self._compute_product_cost_price(
|
|
'in', self.unit_price_company)
|
|
return cost_price, to_save
|
|
|
|
@property
|
|
def _cost_price_pattern(self):
|
|
pattern = super()._cost_price_pattern
|
|
if self.company.cost_price_warehouse:
|
|
pattern['warehouse'] = (
|
|
self.cost_warehouse.id if self.cost_warehouse else None)
|
|
return pattern
|
|
|
|
def _cost_price_key(self):
|
|
key = super()._cost_price_key()
|
|
if self.company.cost_price_warehouse:
|
|
key += (('warehouse',
|
|
(self.cost_warehouse.id if self.cost_warehouse else None)),
|
|
)
|
|
return key
|
|
|
|
@classmethod
|
|
def _cost_price_context(cls, moves):
|
|
pool = Pool()
|
|
Location = pool.get('stock.location')
|
|
context = super()._cost_price_context(moves)
|
|
if moves[0].company.cost_price_warehouse:
|
|
warehouse = moves[0].cost_warehouse
|
|
locations = Location.search([
|
|
('type', '=', 'storage'),
|
|
['OR',
|
|
('parent', 'child_of',
|
|
warehouse.id if warehouse else []),
|
|
('cost_warehouse', '=',
|
|
warehouse.id if warehouse else None),
|
|
],
|
|
])
|
|
context['locations'] = [l.id for l in locations]
|
|
return context
|
|
|
|
def get_fifo_move(self, quantity=0.0, date=None):
|
|
warehouse = self.cost_warehouse.id if self.cost_warehouse else None
|
|
with Transaction().set_context(warehouse=warehouse):
|
|
return super().get_fifo_move(quantity=quantity, date=date)
|
|
|
|
def _get_account_stock_move_type(self):
|
|
type_ = super()._get_account_stock_move_type()
|
|
if (self.company.cost_price_warehouse
|
|
and self.from_location.type == 'storage'
|
|
and self.to_location.type == 'storage'
|
|
and self.from_cost_warehouse != self.to_cost_warehouse):
|
|
if self.from_cost_warehouse and not self.to_cost_warehouse:
|
|
type_ = 'out_warehouse'
|
|
elif not self.from_cost_warehouse and self.to_cost_warehouse:
|
|
type_ = 'in_warehouse'
|
|
return type_
|
|
|
|
|
|
class ShipmentInternal(metaclass=PoolMeta):
|
|
__name__ = 'stock.shipment.internal'
|
|
|
|
@fields.depends('company')
|
|
def on_change_with_transit_location(self, name=None):
|
|
pool = Pool()
|
|
Config = pool.get('stock.configuration')
|
|
location = super().on_change_with_transit_location(name=name)
|
|
if not location and self.company and self.company.cost_price_warehouse:
|
|
location = Config(1).get_multivalue(
|
|
'shipment_internal_transit', company=self.company)
|
|
return location
|