# 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